text_delta.c revision 299742
1/* 2 * text-delta.c -- Internal text delta representation 3 * 4 * ==================================================================== 5 * Licensed to the Apache Software Foundation (ASF) under one 6 * or more contributor license agreements. See the NOTICE file 7 * distributed with this work for additional information 8 * regarding copyright ownership. The ASF licenses this file 9 * to you under the Apache License, Version 2.0 (the 10 * "License"); you may not use this file except in compliance 11 * with the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, 16 * software distributed under the License is distributed on an 17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 * KIND, either express or implied. See the License for the 19 * specific language governing permissions and limitations 20 * under the License. 21 * ==================================================================== 22 */ 23 24 25#include <assert.h> 26#include <string.h> 27 28#include <apr_general.h> /* for APR_INLINE */ 29#include <apr_md5.h> /* for, um...MD5 stuff */ 30 31#include "svn_delta.h" 32#include "svn_io.h" 33#include "svn_pools.h" 34#include "svn_checksum.h" 35 36#include "delta.h" 37 38 39/* Text delta stream descriptor. */ 40 41struct svn_txdelta_stream_t { 42 /* Copied from parameters to svn_txdelta_stream_create. */ 43 void *baton; 44 svn_txdelta_next_window_fn_t next_window; 45 svn_txdelta_md5_digest_fn_t md5_digest; 46}; 47 48/* Delta stream baton. */ 49struct txdelta_baton { 50 /* These are copied from parameters passed to svn_txdelta. */ 51 svn_stream_t *source; 52 svn_stream_t *target; 53 54 /* Private data */ 55 svn_boolean_t more_source; /* FALSE if source stream hit EOF. */ 56 svn_boolean_t more; /* TRUE if there are more data in the pool. */ 57 svn_filesize_t pos; /* Offset of next read in source file. */ 58 char *buf; /* Buffer for input data. */ 59 60 svn_checksum_ctx_t *context; /* If not NULL, the context for computing 61 the checksum. */ 62 svn_checksum_t *checksum; /* If non-NULL, the checksum of TARGET. */ 63 64 apr_pool_t *result_pool; /* For results (e.g. checksum) */ 65}; 66 67 68/* Target-push stream descriptor. */ 69 70struct tpush_baton { 71 /* These are copied from parameters passed to svn_txdelta_target_push. */ 72 svn_stream_t *source; 73 svn_txdelta_window_handler_t wh; 74 void *whb; 75 apr_pool_t *pool; 76 77 /* Private data */ 78 char *buf; 79 svn_filesize_t source_offset; 80 apr_size_t source_len; 81 svn_boolean_t source_done; 82 apr_size_t target_len; 83}; 84 85 86/* Text delta applicator. */ 87 88struct apply_baton { 89 /* These are copied from parameters passed to svn_txdelta_apply. */ 90 svn_stream_t *source; 91 svn_stream_t *target; 92 93 /* Private data. Between calls, SBUF contains the data from the 94 * last window's source view, as specified by SBUF_OFFSET and 95 * SBUF_LEN. The contents of TBUF are not interesting between 96 * calls. */ 97 apr_pool_t *pool; /* Pool to allocate data from */ 98 char *sbuf; /* Source buffer */ 99 apr_size_t sbuf_size; /* Allocated source buffer space */ 100 svn_filesize_t sbuf_offset; /* Offset of SBUF data in source stream */ 101 apr_size_t sbuf_len; /* Length of SBUF data */ 102 char *tbuf; /* Target buffer */ 103 apr_size_t tbuf_size; /* Allocated target buffer space */ 104 105 apr_md5_ctx_t md5_context; /* Leads to result_digest below. */ 106 unsigned char *result_digest; /* MD5 digest of resultant fulltext; 107 must point to at least APR_MD5_DIGESTSIZE 108 bytes of storage. */ 109 110 const char *error_info; /* Optional extra info for error returns. */ 111}; 112 113 114 115svn_txdelta_window_t * 116svn_txdelta__make_window(const svn_txdelta__ops_baton_t *build_baton, 117 apr_pool_t *pool) 118{ 119 svn_txdelta_window_t *window; 120 svn_string_t *new_data = apr_palloc(pool, sizeof(*new_data)); 121 122 window = apr_palloc(pool, sizeof(*window)); 123 window->sview_offset = 0; 124 window->sview_len = 0; 125 window->tview_len = 0; 126 127 window->num_ops = build_baton->num_ops; 128 window->src_ops = build_baton->src_ops; 129 window->ops = build_baton->ops; 130 131 /* just copy the fields over, rather than alloc/copying into a whole new 132 svn_string_t structure. */ 133 /* ### would be much nicer if window->new_data were not a ptr... */ 134 new_data->data = build_baton->new_data->data; 135 new_data->len = build_baton->new_data->len; 136 window->new_data = new_data; 137 138 return window; 139} 140 141 142/* Compute and return a delta window using the xdelta algorithm on 143 DATA, which contains SOURCE_LEN bytes of source data and TARGET_LEN 144 bytes of target data. SOURCE_OFFSET gives the offset of the source 145 data, and is simply copied into the window's sview_offset field. */ 146static svn_txdelta_window_t * 147compute_window(const char *data, apr_size_t source_len, apr_size_t target_len, 148 svn_filesize_t source_offset, apr_pool_t *pool) 149{ 150 svn_txdelta__ops_baton_t build_baton = { 0 }; 151 svn_txdelta_window_t *window; 152 153 /* Compute the delta operations. */ 154 build_baton.new_data = svn_stringbuf_create_empty(pool); 155 156 if (source_len == 0) 157 svn_txdelta__insert_op(&build_baton, svn_txdelta_new, 0, target_len, data, 158 pool); 159 else 160 svn_txdelta__xdelta(&build_baton, data, source_len, target_len, pool); 161 162 /* Create and return the delta window. */ 163 window = svn_txdelta__make_window(&build_baton, pool); 164 window->sview_offset = source_offset; 165 window->sview_len = source_len; 166 window->tview_len = target_len; 167 return window; 168} 169 170 171 172svn_txdelta_window_t * 173svn_txdelta_window_dup(const svn_txdelta_window_t *window, 174 apr_pool_t *pool) 175{ 176 svn_txdelta__ops_baton_t build_baton = { 0 }; 177 svn_txdelta_window_t *new_window; 178 const apr_size_t ops_size = (window->num_ops * sizeof(*build_baton.ops)); 179 180 build_baton.num_ops = window->num_ops; 181 build_baton.src_ops = window->src_ops; 182 build_baton.ops_size = window->num_ops; 183 build_baton.ops = apr_palloc(pool, ops_size); 184 memcpy(build_baton.ops, window->ops, ops_size); 185 build_baton.new_data = 186 svn_stringbuf_create_from_string(window->new_data, pool); 187 188 new_window = svn_txdelta__make_window(&build_baton, pool); 189 new_window->sview_offset = window->sview_offset; 190 new_window->sview_len = window->sview_len; 191 new_window->tview_len = window->tview_len; 192 return new_window; 193} 194 195/* This is a private interlibrary compatibility wrapper. */ 196svn_txdelta_window_t * 197svn_txdelta__copy_window(const svn_txdelta_window_t *window, 198 apr_pool_t *pool); 199svn_txdelta_window_t * 200svn_txdelta__copy_window(const svn_txdelta_window_t *window, 201 apr_pool_t *pool) 202{ 203 return svn_txdelta_window_dup(window, pool); 204} 205 206 207/* Insert a delta op into a delta window. */ 208 209void 210svn_txdelta__insert_op(svn_txdelta__ops_baton_t *build_baton, 211 enum svn_delta_action opcode, 212 apr_size_t offset, 213 apr_size_t length, 214 const char *new_data, 215 apr_pool_t *pool) 216{ 217 svn_txdelta_op_t *op; 218 219 /* Check if this op can be merged with the previous op. The delta 220 combiner sometimes generates such ops, and this is the obvious 221 place to make the check. */ 222 if (build_baton->num_ops > 0) 223 { 224 op = &build_baton->ops[build_baton->num_ops - 1]; 225 if (op->action_code == opcode 226 && (opcode == svn_txdelta_new 227 || op->offset + op->length == offset)) 228 { 229 op->length += length; 230 if (opcode == svn_txdelta_new) 231 svn_stringbuf_appendbytes(build_baton->new_data, 232 new_data, length); 233 return; 234 } 235 } 236 237 /* Create space for the new op. */ 238 if (build_baton->num_ops == build_baton->ops_size) 239 { 240 svn_txdelta_op_t *const old_ops = build_baton->ops; 241 int const new_ops_size = (build_baton->ops_size == 0 242 ? 16 : 2 * build_baton->ops_size); 243 build_baton->ops = 244 apr_palloc(pool, new_ops_size * sizeof(*build_baton->ops)); 245 246 /* Copy any existing ops into the new array */ 247 if (old_ops) 248 memcpy(build_baton->ops, old_ops, 249 build_baton->ops_size * sizeof(*build_baton->ops)); 250 build_baton->ops_size = new_ops_size; 251 } 252 253 /* Insert the op. svn_delta_source and svn_delta_target are 254 just inserted. For svn_delta_new, the new data must be 255 copied into the window. */ 256 op = &build_baton->ops[build_baton->num_ops]; 257 switch (opcode) 258 { 259 case svn_txdelta_source: 260 ++build_baton->src_ops; 261 /*** FALLTHRU ***/ 262 case svn_txdelta_target: 263 op->action_code = opcode; 264 op->offset = offset; 265 op->length = length; 266 break; 267 case svn_txdelta_new: 268 op->action_code = opcode; 269 op->offset = build_baton->new_data->len; 270 op->length = length; 271 svn_stringbuf_appendbytes(build_baton->new_data, new_data, length); 272 break; 273 default: 274 assert(!"unknown delta op."); 275 } 276 277 ++build_baton->num_ops; 278} 279 280apr_size_t 281svn_txdelta__remove_copy(svn_txdelta__ops_baton_t *build_baton, 282 apr_size_t max_len) 283{ 284 svn_txdelta_op_t *op; 285 apr_size_t len = 0; 286 287 /* remove ops back to front */ 288 while (build_baton->num_ops > 0) 289 { 290 op = &build_baton->ops[build_baton->num_ops-1]; 291 292 /* we can't modify svn_txdelta_target ops -> stop there */ 293 if (op->action_code == svn_txdelta_target) 294 break; 295 296 /* handle the case that we cannot remove the op entirely */ 297 if (op->length + len > max_len) 298 { 299 /* truncate only insertions. Copies don't benefit 300 from being truncated. */ 301 if (op->action_code == svn_txdelta_new) 302 { 303 build_baton->new_data->len -= max_len - len; 304 op->length -= max_len - len; 305 len = max_len; 306 } 307 308 break; 309 } 310 311 /* drop the op entirely */ 312 if (op->action_code == svn_txdelta_new) 313 build_baton->new_data->len -= op->length; 314 315 len += op->length; 316 --build_baton->num_ops; 317 } 318 319 return len; 320} 321 322 323 324/* Generic delta stream functions. */ 325 326svn_txdelta_stream_t * 327svn_txdelta_stream_create(void *baton, 328 svn_txdelta_next_window_fn_t next_window, 329 svn_txdelta_md5_digest_fn_t md5_digest, 330 apr_pool_t *pool) 331{ 332 svn_txdelta_stream_t *stream = apr_palloc(pool, sizeof(*stream)); 333 334 stream->baton = baton; 335 stream->next_window = next_window; 336 stream->md5_digest = md5_digest; 337 338 return stream; 339} 340 341svn_error_t * 342svn_txdelta_next_window(svn_txdelta_window_t **window, 343 svn_txdelta_stream_t *stream, 344 apr_pool_t *pool) 345{ 346 return stream->next_window(window, stream->baton, pool); 347} 348 349const unsigned char * 350svn_txdelta_md5_digest(svn_txdelta_stream_t *stream) 351{ 352 return stream->md5_digest(stream->baton); 353} 354 355 356 357static svn_error_t * 358txdelta_next_window(svn_txdelta_window_t **window, 359 void *baton, 360 apr_pool_t *pool) 361{ 362 struct txdelta_baton *b = baton; 363 apr_size_t source_len = SVN_DELTA_WINDOW_SIZE; 364 apr_size_t target_len = SVN_DELTA_WINDOW_SIZE; 365 366 /* Read the source stream. */ 367 if (b->more_source) 368 { 369 SVN_ERR(svn_stream_read_full(b->source, b->buf, &source_len)); 370 b->more_source = (source_len == SVN_DELTA_WINDOW_SIZE); 371 } 372 else 373 source_len = 0; 374 375 /* Read the target stream. */ 376 SVN_ERR(svn_stream_read_full(b->target, b->buf + source_len, &target_len)); 377 b->pos += source_len; 378 379 if (target_len == 0) 380 { 381 /* No target data? We're done; return the final window. */ 382 if (b->context != NULL) 383 SVN_ERR(svn_checksum_final(&b->checksum, b->context, b->result_pool)); 384 385 *window = NULL; 386 b->more = FALSE; 387 return SVN_NO_ERROR; 388 } 389 else if (b->context != NULL) 390 SVN_ERR(svn_checksum_update(b->context, b->buf + source_len, target_len)); 391 392 *window = compute_window(b->buf, source_len, target_len, 393 b->pos - source_len, pool); 394 395 /* That's it. */ 396 return SVN_NO_ERROR; 397} 398 399 400static const unsigned char * 401txdelta_md5_digest(void *baton) 402{ 403 struct txdelta_baton *b = baton; 404 /* If there are more windows for this stream, the digest has not yet 405 been calculated. */ 406 if (b->more) 407 return NULL; 408 409 /* If checksumming has not been activated, there will be no digest. */ 410 if (b->context == NULL) 411 return NULL; 412 413 /* The checksum should be there. */ 414 return b->checksum->digest; 415} 416 417 418svn_error_t * 419svn_txdelta_run(svn_stream_t *source, 420 svn_stream_t *target, 421 svn_txdelta_window_handler_t handler, 422 void *handler_baton, 423 svn_checksum_kind_t checksum_kind, 424 svn_checksum_t **checksum, 425 svn_cancel_func_t cancel_func, 426 void *cancel_baton, 427 apr_pool_t *result_pool, 428 apr_pool_t *scratch_pool) 429{ 430 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 431 struct txdelta_baton tb = { 0 }; 432 svn_txdelta_window_t *window; 433 434 tb.source = source; 435 tb.target = target; 436 tb.more_source = TRUE; 437 tb.more = TRUE; 438 tb.pos = 0; 439 tb.buf = apr_palloc(scratch_pool, 2 * SVN_DELTA_WINDOW_SIZE); 440 tb.result_pool = result_pool; 441 442 if (checksum != NULL) 443 tb.context = svn_checksum_ctx_create(checksum_kind, scratch_pool); 444 445 do 446 { 447 /* free the window (if any) */ 448 svn_pool_clear(iterpool); 449 450 /* read in a single delta window */ 451 SVN_ERR(txdelta_next_window(&window, &tb, iterpool)); 452 453 /* shove it at the handler */ 454 SVN_ERR((*handler)(window, handler_baton)); 455 456 if (cancel_func) 457 SVN_ERR(cancel_func(cancel_baton)); 458 } 459 while (window != NULL); 460 461 svn_pool_destroy(iterpool); 462 463 if (checksum != NULL) 464 *checksum = tb.checksum; /* should be there! */ 465 466 return SVN_NO_ERROR; 467} 468 469 470void 471svn_txdelta2(svn_txdelta_stream_t **stream, 472 svn_stream_t *source, 473 svn_stream_t *target, 474 svn_boolean_t calculate_checksum, 475 apr_pool_t *pool) 476{ 477 struct txdelta_baton *b = apr_pcalloc(pool, sizeof(*b)); 478 479 b->source = source; 480 b->target = target; 481 b->more_source = TRUE; 482 b->more = TRUE; 483 b->buf = apr_palloc(pool, 2 * SVN_DELTA_WINDOW_SIZE); 484 b->context = calculate_checksum 485 ? svn_checksum_ctx_create(svn_checksum_md5, pool) 486 : NULL; 487 b->result_pool = pool; 488 489 *stream = svn_txdelta_stream_create(b, txdelta_next_window, 490 txdelta_md5_digest, pool); 491} 492 493void 494svn_txdelta(svn_txdelta_stream_t **stream, 495 svn_stream_t *source, 496 svn_stream_t *target, 497 apr_pool_t *pool) 498{ 499 svn_txdelta2(stream, source, target, TRUE, pool); 500} 501 502 503 504/* Functions for implementing a "target push" delta. */ 505 506/* This is the write handler for a target-push delta stream. It reads 507 * source data, buffers target data, and fires off delta windows when 508 * the target data buffer is full. */ 509static svn_error_t * 510tpush_write_handler(void *baton, const char *data, apr_size_t *len) 511{ 512 struct tpush_baton *tb = baton; 513 apr_size_t chunk_len, data_len = *len; 514 apr_pool_t *pool = svn_pool_create(tb->pool); 515 svn_txdelta_window_t *window; 516 517 while (data_len > 0) 518 { 519 svn_pool_clear(pool); 520 521 /* Make sure we're all full up on source data, if possible. */ 522 if (tb->source_len == 0 && !tb->source_done) 523 { 524 tb->source_len = SVN_DELTA_WINDOW_SIZE; 525 SVN_ERR(svn_stream_read_full(tb->source, tb->buf, &tb->source_len)); 526 if (tb->source_len < SVN_DELTA_WINDOW_SIZE) 527 tb->source_done = TRUE; 528 } 529 530 /* Copy in the target data, up to SVN_DELTA_WINDOW_SIZE. */ 531 chunk_len = SVN_DELTA_WINDOW_SIZE - tb->target_len; 532 if (chunk_len > data_len) 533 chunk_len = data_len; 534 memcpy(tb->buf + tb->source_len + tb->target_len, data, chunk_len); 535 data += chunk_len; 536 data_len -= chunk_len; 537 tb->target_len += chunk_len; 538 539 /* If we're full of target data, compute and fire off a window. */ 540 if (tb->target_len == SVN_DELTA_WINDOW_SIZE) 541 { 542 window = compute_window(tb->buf, tb->source_len, tb->target_len, 543 tb->source_offset, pool); 544 SVN_ERR(tb->wh(window, tb->whb)); 545 tb->source_offset += tb->source_len; 546 tb->source_len = 0; 547 tb->target_len = 0; 548 } 549 } 550 551 svn_pool_destroy(pool); 552 return SVN_NO_ERROR; 553} 554 555 556/* This is the close handler for a target-push delta stream. It sends 557 * a final window if there is any buffered target data, and then sends 558 * a NULL window signifying the end of the window stream. */ 559static svn_error_t * 560tpush_close_handler(void *baton) 561{ 562 struct tpush_baton *tb = baton; 563 svn_txdelta_window_t *window; 564 565 /* Send a final window if we have any residual target data. */ 566 if (tb->target_len > 0) 567 { 568 window = compute_window(tb->buf, tb->source_len, tb->target_len, 569 tb->source_offset, tb->pool); 570 SVN_ERR(tb->wh(window, tb->whb)); 571 } 572 573 /* Send a final NULL window signifying the end. */ 574 return tb->wh(NULL, tb->whb); 575} 576 577 578svn_stream_t * 579svn_txdelta_target_push(svn_txdelta_window_handler_t handler, 580 void *handler_baton, svn_stream_t *source, 581 apr_pool_t *pool) 582{ 583 struct tpush_baton *tb; 584 svn_stream_t *stream; 585 586 /* Initialize baton. */ 587 tb = apr_palloc(pool, sizeof(*tb)); 588 tb->source = source; 589 tb->wh = handler; 590 tb->whb = handler_baton; 591 tb->pool = pool; 592 tb->buf = apr_palloc(pool, 2 * SVN_DELTA_WINDOW_SIZE); 593 tb->source_offset = 0; 594 tb->source_len = 0; 595 tb->source_done = FALSE; 596 tb->target_len = 0; 597 598 /* Create and return writable stream. */ 599 stream = svn_stream_create(tb, pool); 600 svn_stream_set_write(stream, tpush_write_handler); 601 svn_stream_set_close(stream, tpush_close_handler); 602 return stream; 603} 604 605 606 607/* Functions for applying deltas. */ 608 609/* Ensure that BUF has enough space for VIEW_LEN bytes. */ 610static APR_INLINE svn_error_t * 611size_buffer(char **buf, apr_size_t *buf_size, 612 apr_size_t view_len, apr_pool_t *pool) 613{ 614 if (view_len > *buf_size) 615 { 616 *buf_size *= 2; 617 if (*buf_size < view_len) 618 *buf_size = view_len; 619 SVN_ERR_ASSERT(APR_ALIGN_DEFAULT(*buf_size) >= *buf_size); 620 *buf = apr_palloc(pool, *buf_size); 621 } 622 623 return SVN_NO_ERROR; 624} 625 626/* Copy LEN bytes from SOURCE to TARGET. Unlike memmove() or memcpy(), 627 * create repeating patterns if the source and target ranges overlap. 628 * Return a pointer to the first byte after the copied target range. */ 629static APR_INLINE char * 630patterning_copy(char *target, const char *source, apr_size_t len) 631{ 632 /* If the source and target overlap, repeat the overlapping pattern 633 in the target buffer. Always copy from the source buffer because 634 presumably it will be in the L1 cache after the first iteration 635 and doing this should avoid pipeline stalls due to write/read 636 dependencies. */ 637 const apr_size_t overlap = target - source; 638 while (len > overlap) 639 { 640 memcpy(target, source, overlap); 641 target += overlap; 642 len -= overlap; 643 } 644 645 /* Copy any remaining source pattern. */ 646 if (len) 647 { 648 memcpy(target, source, len); 649 target += len; 650 } 651 652 return target; 653} 654 655void 656svn_txdelta_apply_instructions(svn_txdelta_window_t *window, 657 const char *sbuf, char *tbuf, 658 apr_size_t *tlen) 659{ 660 const svn_txdelta_op_t *op; 661 apr_size_t tpos = 0; 662 663 /* Nothing to do for empty buffers. 664 * This check allows for NULL TBUF in that case. */ 665 if (*tlen == 0) 666 return; 667 668 for (op = window->ops; op < window->ops + window->num_ops; op++) 669 { 670 const apr_size_t buf_len = (op->length < *tlen - tpos 671 ? op->length : *tlen - tpos); 672 673 /* Check some invariants common to all instructions. */ 674 assert(tpos + op->length <= window->tview_len); 675 676 switch (op->action_code) 677 { 678 case svn_txdelta_source: 679 /* Copy from source area. */ 680 assert(sbuf); 681 assert(op->offset + op->length <= window->sview_len); 682 memcpy(tbuf + tpos, sbuf + op->offset, buf_len); 683 break; 684 685 case svn_txdelta_target: 686 /* Copy from target area. We can't use memcpy() or the like 687 * since we need a specific semantics for overlapping copies: 688 * they must result in repeating patterns. 689 * Note that most copies won't have overlapping source and 690 * target ranges (they are just a result of self-compressed 691 * data) but a small percentage will. */ 692 assert(op->offset < tpos); 693 patterning_copy(tbuf + tpos, tbuf + op->offset, buf_len); 694 break; 695 696 case svn_txdelta_new: 697 /* Copy from window new area. */ 698 assert(op->offset + op->length <= window->new_data->len); 699 memcpy(tbuf + tpos, 700 window->new_data->data + op->offset, 701 buf_len); 702 break; 703 704 default: 705 assert(!"Invalid delta instruction code"); 706 } 707 708 tpos += op->length; 709 if (tpos >= *tlen) 710 return; /* The buffer is full. */ 711 } 712 713 /* Check that we produced the right amount of data. */ 714 assert(tpos == window->tview_len); 715 *tlen = tpos; 716} 717 718/* Apply WINDOW to the streams given by APPL. */ 719static svn_error_t * 720apply_window(svn_txdelta_window_t *window, void *baton) 721{ 722 struct apply_baton *ab = (struct apply_baton *) baton; 723 apr_size_t len; 724 svn_error_t *err; 725 726 if (window == NULL) 727 { 728 /* We're done; just clean up. */ 729 if (ab->result_digest) 730 apr_md5_final(ab->result_digest, &(ab->md5_context)); 731 732 err = svn_stream_close(ab->target); 733 svn_pool_destroy(ab->pool); 734 735 return err; 736 } 737 738 /* Make sure the source view didn't slide backwards. */ 739 SVN_ERR_ASSERT(window->sview_len == 0 740 || (window->sview_offset >= ab->sbuf_offset 741 && (window->sview_offset + window->sview_len 742 >= ab->sbuf_offset + ab->sbuf_len))); 743 744 /* Make sure there's enough room in the target buffer. */ 745 SVN_ERR(size_buffer(&ab->tbuf, &ab->tbuf_size, window->tview_len, ab->pool)); 746 747 /* Prepare the source buffer for reading from the input stream. */ 748 if (window->sview_offset != ab->sbuf_offset 749 || window->sview_len > ab->sbuf_size) 750 { 751 char *old_sbuf = ab->sbuf; 752 753 /* Make sure there's enough room. */ 754 SVN_ERR(size_buffer(&ab->sbuf, &ab->sbuf_size, window->sview_len, 755 ab->pool)); 756 757 /* If the existing view overlaps with the new view, copy the 758 * overlap to the beginning of the new buffer. */ 759 if ( (apr_size_t)ab->sbuf_offset + ab->sbuf_len 760 > (apr_size_t)window->sview_offset) 761 { 762 apr_size_t start = 763 (apr_size_t)(window->sview_offset - ab->sbuf_offset); 764 memmove(ab->sbuf, old_sbuf + start, ab->sbuf_len - start); 765 ab->sbuf_len -= start; 766 } 767 else 768 ab->sbuf_len = 0; 769 ab->sbuf_offset = window->sview_offset; 770 } 771 772 /* Read the remainder of the source view into the buffer. */ 773 if (ab->sbuf_len < window->sview_len) 774 { 775 len = window->sview_len - ab->sbuf_len; 776 err = svn_stream_read_full(ab->source, ab->sbuf + ab->sbuf_len, &len); 777 if (err == SVN_NO_ERROR && len != window->sview_len - ab->sbuf_len) 778 err = svn_error_create(SVN_ERR_INCOMPLETE_DATA, NULL, 779 "Delta source ended unexpectedly"); 780 if (err != SVN_NO_ERROR) 781 return err; 782 ab->sbuf_len = window->sview_len; 783 } 784 785 /* Apply the window instructions to the source view to generate 786 the target view. */ 787 len = window->tview_len; 788 svn_txdelta_apply_instructions(window, ab->sbuf, ab->tbuf, &len); 789 SVN_ERR_ASSERT(len == window->tview_len); 790 791 /* Write out the output. */ 792 793 /* Just update the context here. */ 794 if (ab->result_digest) 795 apr_md5_update(&(ab->md5_context), ab->tbuf, len); 796 797 return svn_stream_write(ab->target, ab->tbuf, &len); 798} 799 800 801void 802svn_txdelta_apply(svn_stream_t *source, 803 svn_stream_t *target, 804 unsigned char *result_digest, 805 const char *error_info, 806 apr_pool_t *pool, 807 svn_txdelta_window_handler_t *handler, 808 void **handler_baton) 809{ 810 apr_pool_t *subpool = svn_pool_create(pool); 811 struct apply_baton *ab; 812 813 ab = apr_palloc(subpool, sizeof(*ab)); 814 ab->source = source; 815 ab->target = target; 816 ab->pool = subpool; 817 ab->sbuf = NULL; 818 ab->sbuf_size = 0; 819 ab->sbuf_offset = 0; 820 ab->sbuf_len = 0; 821 ab->tbuf = NULL; 822 ab->tbuf_size = 0; 823 ab->result_digest = result_digest; 824 825 if (result_digest) 826 apr_md5_init(&(ab->md5_context)); 827 828 if (error_info) 829 ab->error_info = apr_pstrdup(subpool, error_info); 830 else 831 ab->error_info = NULL; 832 833 *handler = apply_window; 834 *handler_baton = ab; 835} 836 837 838 839/* Convenience routines */ 840 841svn_error_t * 842svn_txdelta_send_string(const svn_string_t *string, 843 svn_txdelta_window_handler_t handler, 844 void *handler_baton, 845 apr_pool_t *pool) 846{ 847 svn_txdelta_window_t window = { 0 }; 848 svn_txdelta_op_t op; 849 850 /* Build a single `new' op */ 851 op.action_code = svn_txdelta_new; 852 op.offset = 0; 853 op.length = string->len; 854 855 /* Build a single window containing a ptr to the string. */ 856 window.tview_len = string->len; 857 window.num_ops = 1; 858 window.ops = &op; 859 window.new_data = string; 860 861 /* Push the one window at the handler. */ 862 SVN_ERR((*handler)(&window, handler_baton)); 863 864 /* Push a NULL at the handler, because we're done. */ 865 return (*handler)(NULL, handler_baton); 866} 867 868svn_error_t *svn_txdelta_send_stream(svn_stream_t *stream, 869 svn_txdelta_window_handler_t handler, 870 void *handler_baton, 871 unsigned char *digest, 872 apr_pool_t *pool) 873{ 874 svn_txdelta_window_t delta_window = { 0 }; 875 svn_txdelta_op_t delta_op; 876 svn_string_t window_data; 877 char read_buf[SVN__STREAM_CHUNK_SIZE + 1]; 878 svn_checksum_ctx_t *md5_checksum_ctx; 879 880 if (digest) 881 md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool); 882 883 while (1) 884 { 885 apr_size_t read_len = SVN__STREAM_CHUNK_SIZE; 886 887 SVN_ERR(svn_stream_read_full(stream, read_buf, &read_len)); 888 if (read_len == 0) 889 break; 890 891 window_data.data = read_buf; 892 window_data.len = read_len; 893 894 delta_op.action_code = svn_txdelta_new; 895 delta_op.offset = 0; 896 delta_op.length = read_len; 897 898 delta_window.tview_len = read_len; 899 delta_window.num_ops = 1; 900 delta_window.ops = &delta_op; 901 delta_window.new_data = &window_data; 902 903 SVN_ERR(handler(&delta_window, handler_baton)); 904 905 if (digest) 906 SVN_ERR(svn_checksum_update(md5_checksum_ctx, read_buf, read_len)); 907 908 if (read_len < SVN__STREAM_CHUNK_SIZE) 909 break; 910 } 911 SVN_ERR(handler(NULL, handler_baton)); 912 913 if (digest) 914 { 915 svn_checksum_t *md5_checksum; 916 917 SVN_ERR(svn_checksum_final(&md5_checksum, md5_checksum_ctx, pool)); 918 memcpy(digest, md5_checksum->digest, APR_MD5_DIGESTSIZE); 919 } 920 921 return SVN_NO_ERROR; 922} 923 924svn_error_t *svn_txdelta_send_txstream(svn_txdelta_stream_t *txstream, 925 svn_txdelta_window_handler_t handler, 926 void *handler_baton, 927 apr_pool_t *pool) 928{ 929 svn_txdelta_window_t *window; 930 931 /* create a pool just for the windows */ 932 apr_pool_t *wpool = svn_pool_create(pool); 933 934 do 935 { 936 /* free the window (if any) */ 937 svn_pool_clear(wpool); 938 939 /* read in a single delta window */ 940 SVN_ERR(svn_txdelta_next_window(&window, txstream, wpool)); 941 942 /* shove it at the handler */ 943 SVN_ERR((*handler)(window, handler_baton)); 944 } 945 while (window != NULL); 946 947 svn_pool_destroy(wpool); 948 949 return SVN_NO_ERROR; 950} 951 952svn_error_t * 953svn_txdelta_send_contents(const unsigned char *contents, 954 apr_size_t len, 955 svn_txdelta_window_handler_t handler, 956 void *handler_baton, 957 apr_pool_t *pool) 958{ 959 svn_string_t new_data; 960 svn_txdelta_op_t op = { svn_txdelta_new, 0, 0 }; 961 svn_txdelta_window_t window = { 0, 0, 0, 1, 0 }; 962 window.ops = &op; 963 window.new_data = &new_data; 964 965 /* send CONTENT as a series of max-sized windows */ 966 while (len > 0) 967 { 968 /* stuff next chunk into the window */ 969 window.tview_len = len < SVN_DELTA_WINDOW_SIZE 970 ? len 971 : SVN_DELTA_WINDOW_SIZE; 972 op.length = window.tview_len; 973 new_data.len = window.tview_len; 974 new_data.data = (const char*)contents; 975 976 /* update remaining */ 977 contents += window.tview_len; 978 len -= window.tview_len; 979 980 /* shove it at the handler */ 981 SVN_ERR((*handler)(&window, handler_baton)); 982 } 983 984 /* indicate end of stream */ 985 SVN_ERR((*handler)(NULL, handler_baton)); 986 987 return SVN_NO_ERROR; 988} 989 990