xref: /freebsd-11-stable/contrib/subversion/subversion/libsvn_delta/text_delta.c (revision 3c9339f7792540596bf97077a8f403e944af7f39)
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   svn_checksum_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_pmemdup(pool, window->ops, ops_size);
184   build_baton.new_data =
185     svn_stringbuf_create_from_string(window->new_data, pool);
186 
187   new_window = svn_txdelta__make_window(&build_baton, pool);
188   new_window->sview_offset = window->sview_offset;
189   new_window->sview_len = window->sview_len;
190   new_window->tview_len = window->tview_len;
191   return new_window;
192 }
193 
194 /* This is a private interlibrary compatibility wrapper. */
195 svn_txdelta_window_t *
196 svn_txdelta__copy_window(const svn_txdelta_window_t *window,
197                          apr_pool_t *pool);
198 svn_txdelta_window_t *
svn_txdelta__copy_window(const svn_txdelta_window_t * window,apr_pool_t * pool)199 svn_txdelta__copy_window(const svn_txdelta_window_t *window,
200                          apr_pool_t *pool)
201 {
202   return svn_txdelta_window_dup(window, pool);
203 }
204 
205 
206 /* Insert a delta op into a delta window. */
207 
208 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)209 svn_txdelta__insert_op(svn_txdelta__ops_baton_t *build_baton,
210                        enum svn_delta_action opcode,
211                        apr_size_t offset,
212                        apr_size_t length,
213                        const char *new_data,
214                        apr_pool_t *pool)
215 {
216   svn_txdelta_op_t *op;
217 
218   /* Check if this op can be merged with the previous op. The delta
219      combiner sometimes generates such ops, and this is the obvious
220      place to make the check. */
221   if (build_baton->num_ops > 0)
222     {
223       op = &build_baton->ops[build_baton->num_ops - 1];
224       if (op->action_code == opcode
225           && (opcode == svn_txdelta_new
226               || op->offset + op->length == offset))
227         {
228           op->length += length;
229           if (opcode == svn_txdelta_new)
230             svn_stringbuf_appendbytes(build_baton->new_data,
231                                       new_data, length);
232           return;
233         }
234     }
235 
236   /* Create space for the new op. */
237   if (build_baton->num_ops == build_baton->ops_size)
238     {
239       svn_txdelta_op_t *const old_ops = build_baton->ops;
240       int const new_ops_size = (build_baton->ops_size == 0
241                                 ? 16 : 2 * build_baton->ops_size);
242       build_baton->ops =
243         apr_palloc(pool, new_ops_size * sizeof(*build_baton->ops));
244 
245       /* Copy any existing ops into the new array */
246       if (old_ops)
247         memcpy(build_baton->ops, old_ops,
248                build_baton->ops_size * sizeof(*build_baton->ops));
249       build_baton->ops_size = new_ops_size;
250     }
251 
252   /* Insert the op. svn_delta_source and svn_delta_target are
253      just inserted. For svn_delta_new, the new data must be
254      copied into the window. */
255   op = &build_baton->ops[build_baton->num_ops];
256   switch (opcode)
257     {
258     case svn_txdelta_source:
259       ++build_baton->src_ops;
260       /*** FALLTHRU ***/
261     case svn_txdelta_target:
262       op->action_code = opcode;
263       op->offset = offset;
264       op->length = length;
265       break;
266     case svn_txdelta_new:
267       op->action_code = opcode;
268       op->offset = build_baton->new_data->len;
269       op->length = length;
270       svn_stringbuf_appendbytes(build_baton->new_data, new_data, length);
271       break;
272     default:
273       assert(!"unknown delta op.");
274     }
275 
276   ++build_baton->num_ops;
277 }
278 
279 apr_size_t
svn_txdelta__remove_copy(svn_txdelta__ops_baton_t * build_baton,apr_size_t max_len)280 svn_txdelta__remove_copy(svn_txdelta__ops_baton_t *build_baton,
281                          apr_size_t max_len)
282 {
283   svn_txdelta_op_t *op;
284   apr_size_t len = 0;
285 
286   /* remove ops back to front */
287   while (build_baton->num_ops > 0)
288     {
289       op = &build_baton->ops[build_baton->num_ops-1];
290 
291       /*  we can't modify svn_txdelta_target ops -> stop there */
292       if (op->action_code == svn_txdelta_target)
293         break;
294 
295       /*  handle the case that we cannot remove the op entirely */
296       if (op->length + len > max_len)
297         {
298           /* truncate only insertions. Copies don't benefit
299              from being truncated. */
300           if (op->action_code == svn_txdelta_new)
301             {
302                build_baton->new_data->len -= max_len - len;
303                op->length -= max_len - len;
304                len = max_len;
305             }
306 
307           break;
308         }
309 
310       /* drop the op entirely */
311       if (op->action_code == svn_txdelta_new)
312         build_baton->new_data->len -= op->length;
313 
314       len += op->length;
315       --build_baton->num_ops;
316     }
317 
318   return len;
319 }
320 
321 
322 
323 /* Generic delta stream functions. */
324 
325 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)326 svn_txdelta_stream_create(void *baton,
327                           svn_txdelta_next_window_fn_t next_window,
328                           svn_txdelta_md5_digest_fn_t md5_digest,
329                           apr_pool_t *pool)
330 {
331   svn_txdelta_stream_t *stream = apr_palloc(pool, sizeof(*stream));
332 
333   stream->baton = baton;
334   stream->next_window = next_window;
335   stream->md5_digest = md5_digest;
336 
337   return stream;
338 }
339 
340 svn_error_t *
svn_txdelta_next_window(svn_txdelta_window_t ** window,svn_txdelta_stream_t * stream,apr_pool_t * pool)341 svn_txdelta_next_window(svn_txdelta_window_t **window,
342                         svn_txdelta_stream_t *stream,
343                         apr_pool_t *pool)
344 {
345   return stream->next_window(window, stream->baton, pool);
346 }
347 
348 const unsigned char *
svn_txdelta_md5_digest(svn_txdelta_stream_t * stream)349 svn_txdelta_md5_digest(svn_txdelta_stream_t *stream)
350 {
351   return stream->md5_digest(stream->baton);
352 }
353 
354 
355 
356 static svn_error_t *
txdelta_next_window(svn_txdelta_window_t ** window,void * baton,apr_pool_t * pool)357 txdelta_next_window(svn_txdelta_window_t **window,
358                     void *baton,
359                     apr_pool_t *pool)
360 {
361   struct txdelta_baton *b = baton;
362   apr_size_t source_len = SVN_DELTA_WINDOW_SIZE;
363   apr_size_t target_len = SVN_DELTA_WINDOW_SIZE;
364 
365   /* Read the source stream. */
366   if (b->more_source)
367     {
368       SVN_ERR(svn_stream_read_full(b->source, b->buf, &source_len));
369       b->more_source = (source_len == SVN_DELTA_WINDOW_SIZE);
370     }
371   else
372     source_len = 0;
373 
374   /* Read the target stream. */
375   SVN_ERR(svn_stream_read_full(b->target, b->buf + source_len, &target_len));
376   b->pos += source_len;
377 
378   if (target_len == 0)
379     {
380       /* No target data?  We're done; return the final window. */
381       if (b->context != NULL)
382         SVN_ERR(svn_checksum_final(&b->checksum, b->context, b->result_pool));
383 
384       *window = NULL;
385       b->more = FALSE;
386       return SVN_NO_ERROR;
387     }
388   else if (b->context != NULL)
389     SVN_ERR(svn_checksum_update(b->context, b->buf + source_len, target_len));
390 
391   *window = compute_window(b->buf, source_len, target_len,
392                            b->pos - source_len, pool);
393 
394   /* That's it. */
395   return SVN_NO_ERROR;
396 }
397 
398 
399 static const unsigned char *
txdelta_md5_digest(void * baton)400 txdelta_md5_digest(void *baton)
401 {
402   struct txdelta_baton *b = baton;
403   /* If there are more windows for this stream, the digest has not yet
404      been calculated.  */
405   if (b->more)
406     return NULL;
407 
408   /* If checksumming has not been activated, there will be no digest. */
409   if (b->context == NULL)
410     return NULL;
411 
412   /* The checksum should be there. */
413   return b->checksum->digest;
414 }
415 
416 
417 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)418 svn_txdelta_run(svn_stream_t *source,
419                 svn_stream_t *target,
420                 svn_txdelta_window_handler_t handler,
421                 void *handler_baton,
422                 svn_checksum_kind_t checksum_kind,
423                 svn_checksum_t **checksum,
424                 svn_cancel_func_t cancel_func,
425                 void *cancel_baton,
426                 apr_pool_t *result_pool,
427                 apr_pool_t *scratch_pool)
428 {
429   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
430   struct txdelta_baton tb = { 0 };
431   svn_txdelta_window_t *window;
432 
433   tb.source = source;
434   tb.target = target;
435   tb.more_source = TRUE;
436   tb.more = TRUE;
437   tb.pos = 0;
438   tb.buf = apr_palloc(scratch_pool, 2 * SVN_DELTA_WINDOW_SIZE);
439   tb.result_pool = result_pool;
440 
441   if (checksum != NULL)
442     tb.context = svn_checksum_ctx_create(checksum_kind, scratch_pool);
443 
444   do
445     {
446       /* free the window (if any) */
447       svn_pool_clear(iterpool);
448 
449       /* read in a single delta window */
450       SVN_ERR(txdelta_next_window(&window, &tb, iterpool));
451 
452       /* shove it at the handler */
453       SVN_ERR((*handler)(window, handler_baton));
454 
455       if (cancel_func)
456         SVN_ERR(cancel_func(cancel_baton));
457     }
458   while (window != NULL);
459 
460   svn_pool_destroy(iterpool);
461 
462   if (checksum != NULL)
463     *checksum = tb.checksum;  /* should be there! */
464 
465   return SVN_NO_ERROR;
466 }
467 
468 
469 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)470 svn_txdelta2(svn_txdelta_stream_t **stream,
471              svn_stream_t *source,
472              svn_stream_t *target,
473              svn_boolean_t calculate_checksum,
474              apr_pool_t *pool)
475 {
476   struct txdelta_baton *b = apr_pcalloc(pool, sizeof(*b));
477 
478   b->source = source;
479   b->target = target;
480   b->more_source = TRUE;
481   b->more = TRUE;
482   b->buf = apr_palloc(pool, 2 * SVN_DELTA_WINDOW_SIZE);
483   b->context = calculate_checksum
484              ? svn_checksum_ctx_create(svn_checksum_md5, pool)
485              : NULL;
486   b->result_pool = pool;
487 
488   *stream = svn_txdelta_stream_create(b, txdelta_next_window,
489                                       txdelta_md5_digest, pool);
490 }
491 
492 void
svn_txdelta(svn_txdelta_stream_t ** stream,svn_stream_t * source,svn_stream_t * target,apr_pool_t * pool)493 svn_txdelta(svn_txdelta_stream_t **stream,
494             svn_stream_t *source,
495             svn_stream_t *target,
496             apr_pool_t *pool)
497 {
498   svn_txdelta2(stream, source, target, TRUE, pool);
499 }
500 
501 
502 
503 /* Functions for implementing a "target push" delta. */
504 
505 /* This is the write handler for a target-push delta stream.  It reads
506  * source data, buffers target data, and fires off delta windows when
507  * the target data buffer is full. */
508 static svn_error_t *
tpush_write_handler(void * baton,const char * data,apr_size_t * len)509 tpush_write_handler(void *baton, const char *data, apr_size_t *len)
510 {
511   struct tpush_baton *tb = baton;
512   apr_size_t chunk_len, data_len = *len;
513   apr_pool_t *pool = svn_pool_create(tb->pool);
514   svn_txdelta_window_t *window;
515 
516   while (data_len > 0)
517     {
518       svn_pool_clear(pool);
519 
520       /* Make sure we're all full up on source data, if possible. */
521       if (tb->source_len == 0 && !tb->source_done)
522         {
523           tb->source_len = SVN_DELTA_WINDOW_SIZE;
524           SVN_ERR(svn_stream_read_full(tb->source, tb->buf, &tb->source_len));
525           if (tb->source_len < SVN_DELTA_WINDOW_SIZE)
526             tb->source_done = TRUE;
527         }
528 
529       /* Copy in the target data, up to SVN_DELTA_WINDOW_SIZE. */
530       chunk_len = SVN_DELTA_WINDOW_SIZE - tb->target_len;
531       if (chunk_len > data_len)
532         chunk_len = data_len;
533       memcpy(tb->buf + tb->source_len + tb->target_len, data, chunk_len);
534       data += chunk_len;
535       data_len -= chunk_len;
536       tb->target_len += chunk_len;
537 
538       /* If we're full of target data, compute and fire off a window. */
539       if (tb->target_len == SVN_DELTA_WINDOW_SIZE)
540         {
541           window = compute_window(tb->buf, tb->source_len, tb->target_len,
542                                   tb->source_offset, pool);
543           SVN_ERR(tb->wh(window, tb->whb));
544           tb->source_offset += tb->source_len;
545           tb->source_len = 0;
546           tb->target_len = 0;
547         }
548     }
549 
550   svn_pool_destroy(pool);
551   return SVN_NO_ERROR;
552 }
553 
554 
555 /* This is the close handler for a target-push delta stream.  It sends
556  * a final window if there is any buffered target data, and then sends
557  * a NULL window signifying the end of the window stream. */
558 static svn_error_t *
tpush_close_handler(void * baton)559 tpush_close_handler(void *baton)
560 {
561   struct tpush_baton *tb = baton;
562   svn_txdelta_window_t *window;
563 
564   /* Send a final window if we have any residual target data. */
565   if (tb->target_len > 0)
566     {
567       window = compute_window(tb->buf, tb->source_len, tb->target_len,
568                               tb->source_offset, tb->pool);
569       SVN_ERR(tb->wh(window, tb->whb));
570     }
571 
572   /* Send a final NULL window signifying the end. */
573   return tb->wh(NULL, tb->whb);
574 }
575 
576 
577 svn_stream_t *
svn_txdelta_target_push(svn_txdelta_window_handler_t handler,void * handler_baton,svn_stream_t * source,apr_pool_t * pool)578 svn_txdelta_target_push(svn_txdelta_window_handler_t handler,
579                         void *handler_baton, svn_stream_t *source,
580                         apr_pool_t *pool)
581 {
582   struct tpush_baton *tb;
583   svn_stream_t *stream;
584 
585   /* Initialize baton. */
586   tb = apr_palloc(pool, sizeof(*tb));
587   tb->source = source;
588   tb->wh = handler;
589   tb->whb = handler_baton;
590   tb->pool = pool;
591   tb->buf = apr_palloc(pool, 2 * SVN_DELTA_WINDOW_SIZE);
592   tb->source_offset = 0;
593   tb->source_len = 0;
594   tb->source_done = FALSE;
595   tb->target_len = 0;
596 
597   /* Create and return writable stream. */
598   stream = svn_stream_create(tb, pool);
599   svn_stream_set_write(stream, tpush_write_handler);
600   svn_stream_set_close(stream, tpush_close_handler);
601   return stream;
602 }
603 
604 
605 
606 /* Functions for applying deltas.  */
607 
608 /* Ensure that BUF has enough space for VIEW_LEN bytes.  */
609 static APR_INLINE svn_error_t *
size_buffer(char ** buf,apr_size_t * buf_size,apr_size_t view_len,apr_pool_t * pool)610 size_buffer(char **buf, apr_size_t *buf_size,
611             apr_size_t view_len, apr_pool_t *pool)
612 {
613   if (view_len > *buf_size)
614     {
615       *buf_size *= 2;
616       if (*buf_size < view_len)
617         *buf_size = view_len;
618       SVN_ERR_ASSERT(APR_ALIGN_DEFAULT(*buf_size) >= *buf_size);
619       *buf = apr_palloc(pool, *buf_size);
620     }
621 
622   return SVN_NO_ERROR;
623 }
624 
625 /* Copy LEN bytes from SOURCE to TARGET.  Unlike memmove() or memcpy(),
626  * create repeating patterns if the source and target ranges overlap.
627  * Return a pointer to the first byte after the copied target range.  */
628 static APR_INLINE char *
patterning_copy(char * target,const char * source,apr_size_t len)629 patterning_copy(char *target, const char *source, apr_size_t len)
630 {
631   /* If the source and target overlap, repeat the overlapping pattern
632      in the target buffer. Always copy from the source buffer because
633      presumably it will be in the L1 cache after the first iteration
634      and doing this should avoid pipeline stalls due to write/read
635      dependencies. */
636   const apr_size_t overlap = target - source;
637   while (len > overlap)
638     {
639       memcpy(target, source, overlap);
640       target += overlap;
641       len -= overlap;
642     }
643 
644   /* Copy any remaining source pattern. */
645   if (len)
646     {
647       memcpy(target, source, len);
648       target += len;
649     }
650 
651   return target;
652 }
653 
654 void
svn_txdelta_apply_instructions(svn_txdelta_window_t * window,const char * sbuf,char * tbuf,apr_size_t * tlen)655 svn_txdelta_apply_instructions(svn_txdelta_window_t *window,
656                                const char *sbuf, char *tbuf,
657                                apr_size_t *tlen)
658 {
659   const svn_txdelta_op_t *op;
660   apr_size_t tpos = 0;
661 
662   /* Nothing to do for empty buffers.
663    * This check allows for NULL TBUF in that case. */
664   if (*tlen == 0)
665     return;
666 
667   for (op = window->ops; op < window->ops + window->num_ops; op++)
668     {
669       const apr_size_t buf_len = (op->length < *tlen - tpos
670                                   ? op->length : *tlen - tpos);
671 
672       /* Check some invariants common to all instructions.  */
673       assert(tpos + op->length <= window->tview_len);
674 
675       switch (op->action_code)
676         {
677         case svn_txdelta_source:
678           /* Copy from source area.  */
679           assert(sbuf);
680           assert(op->offset + op->length <= window->sview_len);
681           memcpy(tbuf + tpos, sbuf + op->offset, buf_len);
682           break;
683 
684         case svn_txdelta_target:
685           /* Copy from target area.  We can't use memcpy() or the like
686            * since we need a specific semantics for overlapping copies:
687            * they must result in repeating patterns.
688            * Note that most copies won't have overlapping source and
689            * target ranges (they are just a result of self-compressed
690            * data) but a small percentage will.  */
691           assert(op->offset < tpos);
692           patterning_copy(tbuf + tpos, tbuf + op->offset, buf_len);
693           break;
694 
695         case svn_txdelta_new:
696           /* Copy from window new area.  */
697           assert(op->offset + op->length <= window->new_data->len);
698           memcpy(tbuf + tpos,
699                  window->new_data->data + op->offset,
700                  buf_len);
701           break;
702 
703         default:
704           assert(!"Invalid delta instruction code");
705         }
706 
707       tpos += op->length;
708       if (tpos >= *tlen)
709         return;                 /* The buffer is full. */
710     }
711 
712   /* Check that we produced the right amount of data.  */
713   assert(tpos == window->tview_len);
714   *tlen = tpos;
715 }
716 
717 /* Apply WINDOW to the streams given by APPL.  */
718 static svn_error_t *
apply_window(svn_txdelta_window_t * window,void * baton)719 apply_window(svn_txdelta_window_t *window, void *baton)
720 {
721   struct apply_baton *ab = (struct apply_baton *) baton;
722   apr_size_t len;
723 
724   if (window == NULL)
725     {
726       svn_error_t *err = SVN_NO_ERROR;
727 
728       /* We're done; just clean up.  */
729       if (ab->result_digest)
730         {
731           svn_checksum_t *md5_checksum;
732 
733           err = svn_checksum_final(&md5_checksum, ab->md5_context, ab->pool);
734           if (!err)
735             memcpy(ab->result_digest, md5_checksum->digest,
736                    svn_checksum_size(md5_checksum));
737         }
738 
739       err = svn_error_compose_create(err, svn_stream_close(ab->target));
740       svn_pool_destroy(ab->pool);
741 
742       return err;
743     }
744 
745   /* Make sure the source view didn't slide backwards.  */
746   SVN_ERR_ASSERT(window->sview_len == 0
747                  || (window->sview_offset >= ab->sbuf_offset
748                      && (window->sview_offset + window->sview_len
749                          >= ab->sbuf_offset + ab->sbuf_len)));
750 
751   /* Make sure there's enough room in the target buffer.  */
752   SVN_ERR(size_buffer(&ab->tbuf, &ab->tbuf_size, window->tview_len, ab->pool));
753 
754   /* Prepare the source buffer for reading from the input stream.  */
755   if (window->sview_offset != ab->sbuf_offset
756       || window->sview_len > ab->sbuf_size)
757     {
758       char *old_sbuf = ab->sbuf;
759 
760       /* Make sure there's enough room.  */
761       SVN_ERR(size_buffer(&ab->sbuf, &ab->sbuf_size, window->sview_len,
762               ab->pool));
763 
764       /* If the existing view overlaps with the new view, copy the
765        * overlap to the beginning of the new buffer.  */
766       if (  (apr_size_t)ab->sbuf_offset + ab->sbuf_len
767           > (apr_size_t)window->sview_offset)
768         {
769           apr_size_t start =
770             (apr_size_t)(window->sview_offset - ab->sbuf_offset);
771           memmove(ab->sbuf, old_sbuf + start, ab->sbuf_len - start);
772           ab->sbuf_len -= start;
773         }
774       else
775         ab->sbuf_len = 0;
776       ab->sbuf_offset = window->sview_offset;
777     }
778 
779   /* Read the remainder of the source view into the buffer.  */
780   if (ab->sbuf_len < window->sview_len)
781     {
782       len = window->sview_len - ab->sbuf_len;
783       SVN_ERR(svn_stream_read_full(ab->source, ab->sbuf + ab->sbuf_len, &len));
784       if (len != window->sview_len - ab->sbuf_len)
785         return svn_error_create(SVN_ERR_INCOMPLETE_DATA, NULL,
786                                 "Delta source ended unexpectedly");
787       ab->sbuf_len = window->sview_len;
788     }
789 
790   /* Apply the window instructions to the source view to generate
791      the target view.  */
792   len = window->tview_len;
793   svn_txdelta_apply_instructions(window, ab->sbuf, ab->tbuf, &len);
794   SVN_ERR_ASSERT(len == window->tview_len);
795 
796   /* Write out the output. */
797 
798   /* Just update the context here. */
799   if (ab->result_digest)
800     SVN_ERR(svn_checksum_update(ab->md5_context, ab->tbuf, len));
801 
802   return svn_stream_write(ab->target, ab->tbuf, &len);
803 }
804 
805 
806 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)807 svn_txdelta_apply(svn_stream_t *source,
808                   svn_stream_t *target,
809                   unsigned char *result_digest,
810                   const char *error_info,
811                   apr_pool_t *pool,
812                   svn_txdelta_window_handler_t *handler,
813                   void **handler_baton)
814 {
815   apr_pool_t *subpool = svn_pool_create(pool);
816   struct apply_baton *ab;
817 
818   ab = apr_palloc(subpool, sizeof(*ab));
819   ab->source = source;
820   ab->target = target;
821   ab->pool = subpool;
822   ab->sbuf = NULL;
823   ab->sbuf_size = 0;
824   ab->sbuf_offset = 0;
825   ab->sbuf_len = 0;
826   ab->tbuf = NULL;
827   ab->tbuf_size = 0;
828   ab->result_digest = result_digest;
829 
830   if (result_digest)
831     ab->md5_context = svn_checksum_ctx_create(svn_checksum_md5, subpool);
832 
833   if (error_info)
834     ab->error_info = apr_pstrdup(subpool, error_info);
835   else
836     ab->error_info = NULL;
837 
838   *handler = apply_window;
839   *handler_baton = ab;
840 }
841 
842 
843 
844 /* Convenience routines */
845 
846 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)847 svn_txdelta_send_string(const svn_string_t *string,
848                         svn_txdelta_window_handler_t handler,
849                         void *handler_baton,
850                         apr_pool_t *pool)
851 {
852   svn_txdelta_window_t window = { 0 };
853   svn_txdelta_op_t op;
854 
855   /* Build a single `new' op */
856   op.action_code = svn_txdelta_new;
857   op.offset = 0;
858   op.length = string->len;
859 
860   /* Build a single window containing a ptr to the string. */
861   window.tview_len = string->len;
862   window.num_ops = 1;
863   window.ops = &op;
864   window.new_data = string;
865 
866   /* Push the one window at the handler. */
867   SVN_ERR((*handler)(&window, handler_baton));
868 
869   /* Push a NULL at the handler, because we're done. */
870   return (*handler)(NULL, handler_baton);
871 }
872 
svn_txdelta_send_stream(svn_stream_t * stream,svn_txdelta_window_handler_t handler,void * handler_baton,unsigned char * digest,apr_pool_t * pool)873 svn_error_t *svn_txdelta_send_stream(svn_stream_t *stream,
874                                      svn_txdelta_window_handler_t handler,
875                                      void *handler_baton,
876                                      unsigned char *digest,
877                                      apr_pool_t *pool)
878 {
879   svn_txdelta_window_t delta_window = { 0 };
880   svn_txdelta_op_t delta_op;
881   svn_string_t window_data;
882   char read_buf[SVN__STREAM_CHUNK_SIZE + 1];
883   svn_checksum_ctx_t *md5_checksum_ctx;
884 
885   if (digest)
886     md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
887 
888   while (1)
889     {
890       apr_size_t read_len = SVN__STREAM_CHUNK_SIZE;
891 
892       SVN_ERR(svn_stream_read_full(stream, read_buf, &read_len));
893       if (read_len == 0)
894         break;
895 
896       window_data.data = read_buf;
897       window_data.len = read_len;
898 
899       delta_op.action_code = svn_txdelta_new;
900       delta_op.offset = 0;
901       delta_op.length = read_len;
902 
903       delta_window.tview_len = read_len;
904       delta_window.num_ops = 1;
905       delta_window.ops = &delta_op;
906       delta_window.new_data = &window_data;
907 
908       SVN_ERR(handler(&delta_window, handler_baton));
909 
910       if (digest)
911         SVN_ERR(svn_checksum_update(md5_checksum_ctx, read_buf, read_len));
912 
913       if (read_len < SVN__STREAM_CHUNK_SIZE)
914         break;
915     }
916   SVN_ERR(handler(NULL, handler_baton));
917 
918   if (digest)
919     {
920       svn_checksum_t *md5_checksum;
921 
922       SVN_ERR(svn_checksum_final(&md5_checksum, md5_checksum_ctx, pool));
923       memcpy(digest, md5_checksum->digest, APR_MD5_DIGESTSIZE);
924     }
925 
926   return SVN_NO_ERROR;
927 }
928 
svn_txdelta_send_txstream(svn_txdelta_stream_t * txstream,svn_txdelta_window_handler_t handler,void * handler_baton,apr_pool_t * pool)929 svn_error_t *svn_txdelta_send_txstream(svn_txdelta_stream_t *txstream,
930                                        svn_txdelta_window_handler_t handler,
931                                        void *handler_baton,
932                                        apr_pool_t *pool)
933 {
934   svn_txdelta_window_t *window;
935 
936   /* create a pool just for the windows */
937   apr_pool_t *wpool = svn_pool_create(pool);
938 
939   do
940     {
941       /* free the window (if any) */
942       svn_pool_clear(wpool);
943 
944       /* read in a single delta window */
945       SVN_ERR(svn_txdelta_next_window(&window, txstream, wpool));
946 
947       /* shove it at the handler */
948       SVN_ERR((*handler)(window, handler_baton));
949     }
950   while (window != NULL);
951 
952   svn_pool_destroy(wpool);
953 
954   return SVN_NO_ERROR;
955 }
956 
957 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)958 svn_txdelta_send_contents(const unsigned char *contents,
959                           apr_size_t len,
960                           svn_txdelta_window_handler_t handler,
961                           void *handler_baton,
962                           apr_pool_t *pool)
963 {
964   svn_string_t new_data;
965   svn_txdelta_op_t op = { svn_txdelta_new, 0, 0 };
966   svn_txdelta_window_t window = { 0, 0, 0, 1, 0 };
967   window.ops = &op;
968   window.new_data = &new_data;
969 
970   /* send CONTENT as a series of max-sized windows */
971   while (len > 0)
972     {
973       /* stuff next chunk into the window */
974       window.tview_len = len < SVN_DELTA_WINDOW_SIZE
975                        ? len
976                        : SVN_DELTA_WINDOW_SIZE;
977       op.length = window.tview_len;
978       new_data.len = window.tview_len;
979       new_data.data = (const char*)contents;
980 
981       /* update remaining */
982       contents += window.tview_len;
983       len -= window.tview_len;
984 
985       /* shove it at the handler */
986       SVN_ERR((*handler)(&window, handler_baton));
987     }
988 
989   /* indicate end of stream */
990   SVN_ERR((*handler)(NULL, handler_baton));
991 
992   return SVN_NO_ERROR;
993 }
994 
995