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
41 struct 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. */
49 struct 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
70 struct 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
88 struct 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
115 svn_txdelta_window_t *
svn_txdelta__make_window(const svn_txdelta__ops_baton_t * build_baton,apr_pool_t * pool)116 svn_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. */
146 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)147 compute_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
172 svn_txdelta_window_t *
svn_txdelta_window_dup(const svn_txdelta_window_t * window,apr_pool_t * pool)173 svn_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. */
196 svn_txdelta_window_t *
197 svn_txdelta__copy_window(const svn_txdelta_window_t *window,
198 apr_pool_t *pool);
199 svn_txdelta_window_t *
svn_txdelta__copy_window(const svn_txdelta_window_t * window,apr_pool_t * pool)200 svn_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
209 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)210 svn_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
280 apr_size_t
svn_txdelta__remove_copy(svn_txdelta__ops_baton_t * build_baton,apr_size_t max_len)281 svn_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
326 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)327 svn_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
341 svn_error_t *
svn_txdelta_next_window(svn_txdelta_window_t ** window,svn_txdelta_stream_t * stream,apr_pool_t * pool)342 svn_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
349 const unsigned char *
svn_txdelta_md5_digest(svn_txdelta_stream_t * stream)350 svn_txdelta_md5_digest(svn_txdelta_stream_t *stream)
351 {
352 return stream->md5_digest(stream->baton);
353 }
354
355
356
357 static svn_error_t *
txdelta_next_window(svn_txdelta_window_t ** window,void * baton,apr_pool_t * pool)358 txdelta_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
400 static const unsigned char *
txdelta_md5_digest(void * baton)401 txdelta_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
418 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)419 svn_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
470 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)471 svn_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
493 void
svn_txdelta(svn_txdelta_stream_t ** stream,svn_stream_t * source,svn_stream_t * target,apr_pool_t * pool)494 svn_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. */
509 static svn_error_t *
tpush_write_handler(void * baton,const char * data,apr_size_t * len)510 tpush_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. */
559 static svn_error_t *
tpush_close_handler(void * baton)560 tpush_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
578 svn_stream_t *
svn_txdelta_target_push(svn_txdelta_window_handler_t handler,void * handler_baton,svn_stream_t * source,apr_pool_t * pool)579 svn_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. */
610 static APR_INLINE svn_error_t *
size_buffer(char ** buf,apr_size_t * buf_size,apr_size_t view_len,apr_pool_t * pool)611 size_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. */
629 static APR_INLINE char *
patterning_copy(char * target,const char * source,apr_size_t len)630 patterning_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
655 void
svn_txdelta_apply_instructions(svn_txdelta_window_t * window,const char * sbuf,char * tbuf,apr_size_t * tlen)656 svn_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. */
719 static svn_error_t *
apply_window(svn_txdelta_window_t * window,void * baton)720 apply_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
801 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)802 svn_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
841 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)842 svn_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
svn_txdelta_send_stream(svn_stream_t * stream,svn_txdelta_window_handler_t handler,void * handler_baton,unsigned char * digest,apr_pool_t * pool)868 svn_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
svn_txdelta_send_txstream(svn_txdelta_stream_t * txstream,svn_txdelta_window_handler_t handler,void * handler_baton,apr_pool_t * pool)924 svn_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
952 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)953 svn_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