]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/subversion/subversion/libsvn_fs_base/reps-strings.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / contrib / subversion / subversion / libsvn_fs_base / reps-strings.c
1 /* reps-strings.c : intepreting representations with respect to strings
2  *
3  * ====================================================================
4  *    Licensed to the Apache Software Foundation (ASF) under one
5  *    or more contributor license agreements.  See the NOTICE file
6  *    distributed with this work for additional information
7  *    regarding copyright ownership.  The ASF licenses this file
8  *    to you under the Apache License, Version 2.0 (the
9  *    "License"); you may not use this file except in compliance
10  *    with the License.  You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
14  *    Unless required by applicable law or agreed to in writing,
15  *    software distributed under the License is distributed on an
16  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17  *    KIND, either express or implied.  See the License for the
18  *    specific language governing permissions and limitations
19  *    under the License.
20  * ====================================================================
21  */
22
23 #include <assert.h>
24
25 #include "svn_fs.h"
26 #include "svn_pools.h"
27
28 #include "fs.h"
29 #include "err.h"
30 #include "trail.h"
31 #include "reps-strings.h"
32
33 #include "bdb/reps-table.h"
34 #include "bdb/strings-table.h"
35
36 #include "../libsvn_fs/fs-loader.h"
37 #define SVN_WANT_BDB
38 #include "svn_private_config.h"
39
40 \f
41 /*** Helper Functions ***/
42
43
44 /* Return non-zero iff REP is mutable under transaction TXN_ID. */
45 static svn_boolean_t rep_is_mutable(representation_t *rep,
46                                     const char *txn_id)
47 {
48   if ((! rep->txn_id) || (strcmp(rep->txn_id, txn_id) != 0))
49     return FALSE;
50   return TRUE;
51 }
52
53 /* Helper macro that evaluates to an error message indicating that
54    the representation referred to by X has an unknown node kind. */
55 #define UNKNOWN_NODE_KIND(x)                                   \
56   svn_error_createf                                            \
57     (SVN_ERR_FS_CORRUPT, NULL,                                 \
58      _("Unknown node kind for representation '%s'"), x)
59
60 /* Return a `fulltext' representation, allocated in POOL, which
61  * references the string STR_KEY.
62  *
63  * If TXN_ID is non-zero and non-NULL, make the representation mutable
64  * under that TXN_ID.
65  *
66  * If STR_KEY is non-null, copy it into an allocation from POOL.
67  *
68  * If MD5_CHECKSUM is non-null, use it as the MD5 checksum for the new
69  * rep; else initialize the rep with an all-zero (i.e., always
70  * successful) MD5 checksum.
71  *
72  * If SHA1_CHECKSUM is non-null, use it as the SHA1 checksum for the new
73  * rep; else initialize the rep with an all-zero (i.e., always
74  * successful) SHA1 checksum.
75  */
76 static representation_t *
77 make_fulltext_rep(const char *str_key,
78                   const char *txn_id,
79                   svn_checksum_t *md5_checksum,
80                   svn_checksum_t *sha1_checksum,
81                   apr_pool_t *pool)
82
83 {
84   representation_t *rep = apr_pcalloc(pool, sizeof(*rep));
85   if (txn_id && *txn_id)
86     rep->txn_id = apr_pstrdup(pool, txn_id);
87   rep->kind = rep_kind_fulltext;
88   rep->md5_checksum = svn_checksum_dup(md5_checksum, pool);
89   rep->sha1_checksum = svn_checksum_dup(sha1_checksum, pool);
90   rep->contents.fulltext.string_key
91     = str_key ? apr_pstrdup(pool, str_key) : NULL;
92   return rep;
93 }
94
95
96 /* Set *KEYS to an array of string keys gleaned from `delta'
97    representation REP.  Allocate *KEYS in POOL. */
98 static svn_error_t *
99 delta_string_keys(apr_array_header_t **keys,
100                   const representation_t *rep,
101                   apr_pool_t *pool)
102 {
103   const char *key;
104   int i;
105   apr_array_header_t *chunks;
106
107   if (rep->kind != rep_kind_delta)
108     return svn_error_create
109       (SVN_ERR_FS_GENERAL, NULL,
110        _("Representation is not of type 'delta'"));
111
112   /* Set up a convenience variable. */
113   chunks = rep->contents.delta.chunks;
114
115   /* Initialize *KEYS to an empty array. */
116   *keys = apr_array_make(pool, chunks->nelts, sizeof(key));
117   if (! chunks->nelts)
118     return SVN_NO_ERROR;
119
120   /* Now, push the string keys for each window into *KEYS */
121   for (i = 0; i < chunks->nelts; i++)
122     {
123       rep_delta_chunk_t *chunk = APR_ARRAY_IDX(chunks, i, rep_delta_chunk_t *);
124
125       key = apr_pstrdup(pool, chunk->string_key);
126       APR_ARRAY_PUSH(*keys, const char *) = key;
127     }
128
129   return SVN_NO_ERROR;
130 }
131
132
133 /* Delete the strings associated with array KEYS in FS as part of TRAIL.  */
134 static svn_error_t *
135 delete_strings(const apr_array_header_t *keys,
136                svn_fs_t *fs,
137                trail_t *trail,
138                apr_pool_t *pool)
139 {
140   int i;
141   const char *str_key;
142   apr_pool_t *subpool = svn_pool_create(pool);
143
144   for (i = 0; i < keys->nelts; i++)
145     {
146       svn_pool_clear(subpool);
147       str_key = APR_ARRAY_IDX(keys, i, const char *);
148       SVN_ERR(svn_fs_bdb__string_delete(fs, str_key, trail, subpool));
149     }
150   svn_pool_destroy(subpool);
151   return SVN_NO_ERROR;
152 }
153
154
155 \f
156 /*** Reading the contents from a representation. ***/
157
158 struct compose_handler_baton
159 {
160   /* The combined window, and the pool it's allocated from. */
161   svn_txdelta_window_t *window;
162   apr_pool_t *window_pool;
163
164   /* If the incoming window was self-compressed, and the combined WINDOW
165      exists from previous iterations, SOURCE_BUF will point to the
166      expanded self-compressed window. */
167   char *source_buf;
168
169   /* The trail for this operation. WINDOW_POOL will be a child of
170      TRAIL->pool. No allocations will be made from TRAIL->pool itself. */
171   trail_t *trail;
172
173   /* TRUE when no more windows have to be read/combined. */
174   svn_boolean_t done;
175
176   /* TRUE if we've just started reading a new window. We need this
177      because the svndiff handler will push a NULL window at the end of
178      the stream, and we have to ignore that; but we must also know
179      when it's appropriate to push a NULL window at the combiner. */
180   svn_boolean_t init;
181 };
182
183
184 /* Handle one window. If BATON is emtpy, copy the WINDOW into it;
185    otherwise, combine WINDOW with the one in BATON, unless WINDOW
186    is self-compressed (i.e., does not copy from the source view),
187    in which case expand. */
188
189 static svn_error_t *
190 compose_handler(svn_txdelta_window_t *window, void *baton)
191 {
192   struct compose_handler_baton *cb = baton;
193   SVN_ERR_ASSERT(!cb->done || window == NULL);
194   SVN_ERR_ASSERT(cb->trail && cb->trail->pool);
195
196   if (!cb->init && !window)
197     return SVN_NO_ERROR;
198
199   /* We should never get here if we've already expanded a
200      self-compressed window. */
201   SVN_ERR_ASSERT(!cb->source_buf);
202
203   if (cb->window)
204     {
205       if (window && (window->sview_len == 0 || window->src_ops == 0))
206         {
207           /* This is a self-compressed window. Don't combine it with
208              the others, because the combiner may go quadratic. Instead,
209              expand it here and signal that the combination has
210              ended. */
211           apr_size_t source_len = window->tview_len;
212           SVN_ERR_ASSERT(cb->window->sview_len == source_len);
213           cb->source_buf = apr_palloc(cb->window_pool, source_len);
214           svn_txdelta_apply_instructions(window, NULL,
215                                          cb->source_buf, &source_len);
216           cb->done = TRUE;
217         }
218       else
219         {
220           /* Combine the incoming window with whatever's in the baton. */
221           apr_pool_t *composite_pool = svn_pool_create(cb->trail->pool);
222           svn_txdelta_window_t *composite;
223
224           composite = svn_txdelta_compose_windows(window, cb->window,
225                                                   composite_pool);
226           svn_pool_destroy(cb->window_pool);
227           cb->window = composite;
228           cb->window_pool = composite_pool;
229           cb->done = (composite->sview_len == 0 || composite->src_ops == 0);
230         }
231     }
232   else if (window)
233     {
234       /* Copy the (first) window into the baton. */
235       apr_pool_t *window_pool = svn_pool_create(cb->trail->pool);
236       SVN_ERR_ASSERT(cb->window_pool == NULL);
237       cb->window = svn_txdelta_window_dup(window, window_pool);
238       cb->window_pool = window_pool;
239       cb->done = (window->sview_len == 0 || window->src_ops == 0);
240     }
241   else
242     cb->done = TRUE;
243
244   cb->init = FALSE;
245   return SVN_NO_ERROR;
246 }
247
248
249 \f
250 /* Read one delta window from REP[CUR_CHUNK] and push it at the
251    composition handler. */
252
253 static svn_error_t *
254 get_one_window(struct compose_handler_baton *cb,
255                svn_fs_t *fs,
256                representation_t *rep,
257                int cur_chunk)
258 {
259   svn_stream_t *wstream;
260   char diffdata[4096];   /* hunk of svndiff data */
261   svn_filesize_t off;    /* offset into svndiff data */
262   apr_size_t amt;        /* how much svndiff data to/was read */
263   const char *str_key;
264
265   apr_array_header_t *chunks = rep->contents.delta.chunks;
266   rep_delta_chunk_t *this_chunk, *first_chunk;
267
268   cb->init = TRUE;
269   if (chunks->nelts <= cur_chunk)
270     return compose_handler(NULL, cb);
271
272   /* Set up a window handling stream for the svndiff data. */
273   wstream = svn_txdelta_parse_svndiff(compose_handler, cb, TRUE,
274                                       cb->trail->pool);
275
276   /* First things first:  send the "SVN"{version} header through the
277      stream.  ### For now, we will just use the version specified
278      in the first chunk, and then verify that no chunks have a
279      different version number than the one used.  In the future,
280      we might simply convert chunks that use a different version
281      of the diff format -- or, heck, a different format
282      altogether -- to the format/version of the first chunk.  */
283   first_chunk = APR_ARRAY_IDX(chunks, 0, rep_delta_chunk_t*);
284   diffdata[0] = 'S';
285   diffdata[1] = 'V';
286   diffdata[2] = 'N';
287   diffdata[3] = (char) (first_chunk->version);
288   amt = 4;
289   SVN_ERR(svn_stream_write(wstream, diffdata, &amt));
290   /* FIXME: The stream write handler is borked; assert (amt == 4); */
291
292   /* Get this string key which holds this window's data.
293      ### todo: make sure this is an `svndiff' DIFF skel here. */
294   this_chunk = APR_ARRAY_IDX(chunks, cur_chunk, rep_delta_chunk_t*);
295   str_key = this_chunk->string_key;
296
297   /* Run through the svndiff data, at least as far as necessary. */
298   off = 0;
299   do
300     {
301       amt = sizeof(diffdata);
302       SVN_ERR(svn_fs_bdb__string_read(fs, str_key, diffdata,
303                                       off, &amt, cb->trail,
304                                       cb->trail->pool));
305       off += amt;
306       SVN_ERR(svn_stream_write(wstream, diffdata, &amt));
307     }
308   while (amt != 0);
309   SVN_ERR(svn_stream_close(wstream));
310
311   SVN_ERR_ASSERT(!cb->init);
312   SVN_ERR_ASSERT(cb->window != NULL);
313   SVN_ERR_ASSERT(cb->window_pool != NULL);
314   return SVN_NO_ERROR;
315 }
316
317
318 /* Undeltify a range of data. DELTAS is the set of delta windows to
319    combine, FULLTEXT is the source text, CUR_CHUNK is the index of the
320    delta chunk we're starting from. OFFSET is the relative offset of
321    the requested data within the chunk; BUF and LEN are what we're
322    undeltifying to. */
323
324 static svn_error_t *
325 rep_undeltify_range(svn_fs_t *fs,
326                     const apr_array_header_t *deltas,
327                     representation_t *fulltext,
328                     int cur_chunk,
329                     char *buf,
330                     apr_size_t offset,
331                     apr_size_t *len,
332                     trail_t *trail,
333                     apr_pool_t *pool)
334 {
335   apr_size_t len_read = 0;
336
337   do
338     {
339       struct compose_handler_baton cb = { 0 };
340       char *source_buf, *target_buf;
341       apr_size_t target_len;
342       int cur_rep;
343
344       cb.trail = trail;
345       cb.done = FALSE;
346       for (cur_rep = 0; !cb.done && cur_rep < deltas->nelts; ++cur_rep)
347         {
348           representation_t *const rep =
349             APR_ARRAY_IDX(deltas, cur_rep, representation_t*);
350           SVN_ERR(get_one_window(&cb, fs, rep, cur_chunk));
351         }
352
353       if (!cb.window)
354           /* That's it, no more source data is available. */
355           break;
356
357       /* The source view length should not be 0 if there are source
358          copy ops in the window. */
359       SVN_ERR_ASSERT(cb.window->sview_len > 0 || cb.window->src_ops == 0);
360
361       /* cb.window is the combined delta window. Read the source text
362          into a buffer. */
363       if (cb.source_buf)
364         {
365           /* The combiner already created the source text from a
366              self-compressed window. */
367           source_buf = cb.source_buf;
368         }
369       else if (fulltext && cb.window->sview_len > 0 && cb.window->src_ops > 0)
370         {
371           apr_size_t source_len = cb.window->sview_len;
372           source_buf = apr_palloc(cb.window_pool, source_len);
373           SVN_ERR(svn_fs_bdb__string_read
374                   (fs, fulltext->contents.fulltext.string_key,
375                    source_buf, cb.window->sview_offset, &source_len,
376                    trail, pool));
377           if (source_len != cb.window->sview_len)
378             return svn_error_create
379                 (SVN_ERR_FS_CORRUPT, NULL,
380                  _("Svndiff source length inconsistency"));
381         }
382       else
383         {
384           source_buf = NULL;    /* Won't read anything from here. */
385         }
386
387       if (offset > 0)
388         {
389           target_len = *len - len_read + offset;
390           target_buf = apr_palloc(cb.window_pool, target_len);
391         }
392       else
393         {
394           target_len = *len - len_read;
395           target_buf = buf;
396         }
397
398       svn_txdelta_apply_instructions(cb.window, source_buf,
399                                      target_buf, &target_len);
400       if (offset > 0)
401         {
402           SVN_ERR_ASSERT(target_len > offset);
403           target_len -= offset;
404           memcpy(buf, target_buf + offset, target_len);
405           offset = 0; /* Read from the beginning of the next chunk. */
406         }
407       /* Don't need this window any more. */
408       svn_pool_destroy(cb.window_pool);
409
410       len_read += target_len;
411       buf += target_len;
412       ++cur_chunk;
413     }
414   while (len_read < *len);
415
416   *len = len_read;
417   return SVN_NO_ERROR;
418 }
419
420
421 \f
422 /* Calculate the index of the chunk in REP that contains REP_OFFSET,
423    and find the relative CHUNK_OFFSET within the chunk.
424    Return -1 if offset is beyond the end of the represented data.
425    ### The basic assumption is that all delta windows are the same size
426    and aligned at the same offset, so this number is the same in all
427    dependent deltas.  Oh, and the chunks in REP must be ordered. */
428
429 static int
430 get_chunk_offset(representation_t *rep,
431                  svn_filesize_t rep_offset,
432                  apr_size_t *chunk_offset)
433 {
434   const apr_array_header_t *chunks = rep->contents.delta.chunks;
435   int cur_chunk;
436   assert(chunks->nelts);
437
438   /* ### Yes, this is a linear search.  I'll change this to bisection
439      the very second we notice it's slowing us down. */
440   for (cur_chunk = 0; cur_chunk < chunks->nelts; ++cur_chunk)
441   {
442     const rep_delta_chunk_t *const this_chunk
443       = APR_ARRAY_IDX(chunks, cur_chunk, rep_delta_chunk_t*);
444
445     if ((this_chunk->offset + this_chunk->size) > rep_offset)
446       {
447         assert(this_chunk->offset <= rep_offset);
448         assert(rep_offset - this_chunk->offset < SVN_MAX_OBJECT_SIZE);
449         *chunk_offset = (apr_size_t) (rep_offset - this_chunk->offset);
450         return cur_chunk;
451       }
452   }
453
454   return -1;
455 }
456
457 /* Copy into BUF *LEN bytes starting at OFFSET from the string
458    represented via REP_KEY in FS, as part of TRAIL.
459    The number of bytes actually copied is stored in *LEN.  */
460 static svn_error_t *
461 rep_read_range(svn_fs_t *fs,
462                const char *rep_key,
463                svn_filesize_t offset,
464                char *buf,
465                apr_size_t *len,
466                trail_t *trail,
467                apr_pool_t *pool)
468 {
469   representation_t *rep;
470   apr_size_t chunk_offset;
471
472   /* Read in our REP. */
473   SVN_ERR(svn_fs_bdb__read_rep(&rep, fs, rep_key, trail, pool));
474   if (rep->kind == rep_kind_fulltext)
475     {
476       SVN_ERR(svn_fs_bdb__string_read(fs, rep->contents.fulltext.string_key,
477                                       buf, offset, len, trail, pool));
478     }
479   else if (rep->kind == rep_kind_delta)
480     {
481       const int cur_chunk = get_chunk_offset(rep, offset, &chunk_offset);
482       if (cur_chunk < 0)
483         *len = 0;
484       else
485         {
486           svn_error_t *err;
487           /* Preserve for potential use in error message. */
488           const char *first_rep_key = rep_key;
489           /* Make a list of all the rep's we need to undeltify this range.
490              We'll have to read them within this trail anyway, so we might
491              as well do it once and up front. */
492           apr_array_header_t *reps = apr_array_make(pool, 30, sizeof(rep));
493           do
494             {
495               const rep_delta_chunk_t *const first_chunk
496                 = APR_ARRAY_IDX(rep->contents.delta.chunks,
497                                 0, rep_delta_chunk_t*);
498               const rep_delta_chunk_t *const chunk
499                 = APR_ARRAY_IDX(rep->contents.delta.chunks,
500                                 cur_chunk, rep_delta_chunk_t*);
501
502               /* Verify that this chunk is of the same version as the first. */
503               if (first_chunk->version != chunk->version)
504                 return svn_error_createf
505                   (SVN_ERR_FS_CORRUPT, NULL,
506                    _("Diff version inconsistencies in representation '%s'"),
507                    rep_key);
508
509               rep_key = chunk->rep_key;
510               APR_ARRAY_PUSH(reps, representation_t *) = rep;
511               SVN_ERR(svn_fs_bdb__read_rep(&rep, fs, rep_key,
512                                            trail, pool));
513             }
514           while (rep->kind == rep_kind_delta
515                  && rep->contents.delta.chunks->nelts > cur_chunk);
516
517           /* Right. We've either just read the fulltext rep, or a rep that's
518              too short, in which case we'll undeltify without source data.*/
519           if (rep->kind != rep_kind_delta && rep->kind != rep_kind_fulltext)
520             return UNKNOWN_NODE_KIND(rep_key);
521
522           if (rep->kind == rep_kind_delta)
523             rep = NULL;         /* Don't use source data */
524
525           err = rep_undeltify_range(fs, reps, rep, cur_chunk, buf,
526                                     chunk_offset, len, trail, pool);
527           if (err)
528             {
529               if (err->apr_err == SVN_ERR_FS_CORRUPT)
530                 return svn_error_createf
531                   (SVN_ERR_FS_CORRUPT, err,
532                    _("Corruption detected whilst reading delta chain from "
533                      "representation '%s' to '%s'"), first_rep_key, rep_key);
534               else
535                 return svn_error_trace(err);
536             }
537         }
538     }
539   else /* unknown kind */
540     return UNKNOWN_NODE_KIND(rep_key);
541
542   return SVN_NO_ERROR;
543 }
544
545
546 svn_error_t *
547 svn_fs_base__get_mutable_rep(const char **new_rep_key,
548                              const char *rep_key,
549                              svn_fs_t *fs,
550                              const char *txn_id,
551                              trail_t *trail,
552                              apr_pool_t *pool)
553 {
554   representation_t *rep = NULL;
555   const char *new_str = NULL;
556
557   /* We were passed an existing REP_KEY, so examine it.  If it is
558      mutable already, then just return REP_KEY as the mutable result
559      key.  */
560   if (rep_key && (rep_key[0] != '\0'))
561     {
562       SVN_ERR(svn_fs_bdb__read_rep(&rep, fs, rep_key, trail, pool));
563       if (rep_is_mutable(rep, txn_id))
564         {
565           *new_rep_key = rep_key;
566           return SVN_NO_ERROR;
567         }
568     }
569
570   /* Either we weren't provided a base key to examine, or the base key
571      we were provided was not mutable.  So, let's make a new
572      representation and return its key to the caller. */
573   SVN_ERR(svn_fs_bdb__string_append(fs, &new_str, 0, NULL, trail, pool));
574   rep = make_fulltext_rep(new_str, txn_id,
575                           svn_checksum_empty_checksum(svn_checksum_md5,
576                                                       pool),
577                           svn_checksum_empty_checksum(svn_checksum_sha1,
578                                                       pool),
579                           pool);
580   return svn_fs_bdb__write_new_rep(new_rep_key, fs, rep, trail, pool);
581 }
582
583
584 svn_error_t *
585 svn_fs_base__delete_rep_if_mutable(svn_fs_t *fs,
586                                    const char *rep_key,
587                                    const char *txn_id,
588                                    trail_t *trail,
589                                    apr_pool_t *pool)
590 {
591   representation_t *rep;
592
593   SVN_ERR(svn_fs_bdb__read_rep(&rep, fs, rep_key, trail, pool));
594   if (! rep_is_mutable(rep, txn_id))
595     return SVN_NO_ERROR;
596
597   if (rep->kind == rep_kind_fulltext)
598     {
599       SVN_ERR(svn_fs_bdb__string_delete(fs,
600                                         rep->contents.fulltext.string_key,
601                                         trail, pool));
602     }
603   else if (rep->kind == rep_kind_delta)
604     {
605       apr_array_header_t *keys;
606       SVN_ERR(delta_string_keys(&keys, rep, pool));
607       SVN_ERR(delete_strings(keys, fs, trail, pool));
608     }
609   else /* unknown kind */
610     return UNKNOWN_NODE_KIND(rep_key);
611
612   return svn_fs_bdb__delete_rep(fs, rep_key, trail, pool);
613 }
614
615
616 \f
617 /*** Reading and writing data via representations. ***/
618
619 /** Reading. **/
620
621 struct rep_read_baton
622 {
623   /* The FS from which we're reading. */
624   svn_fs_t *fs;
625
626   /* The representation skel whose contents we want to read.  If this
627      is NULL, the rep has never had any contents, so all reads fetch 0
628      bytes.
629
630      Formerly, we cached the entire rep skel here, not just the key.
631      That way we didn't have to fetch the rep from the db every time
632      we want to read a little bit more of the file.  Unfortunately,
633      this has a problem: if, say, a file's representation changes
634      while we're reading (changes from fulltext to delta, for
635      example), we'll never know it.  So for correctness, we now
636      refetch the representation skel every time we want to read
637      another chunk.  */
638   const char *rep_key;
639
640   /* How many bytes have been read already. */
641   svn_filesize_t offset;
642
643   /* If present, the read will be done as part of this trail, and the
644      trail's pool will be used.  Otherwise, see `pool' below.  */
645   trail_t *trail;
646
647   /* MD5 checksum context.  Initialized when the baton is created, updated as
648      we read data, and finalized when the stream is closed. */
649   svn_checksum_ctx_t *md5_checksum_ctx;
650
651   /* Final resting place of the checksum created by md5_checksum_cxt. */
652   svn_checksum_t *md5_checksum;
653
654   /* SHA1 checksum context.  Initialized when the baton is created, updated as
655      we read data, and finalized when the stream is closed. */
656   svn_checksum_ctx_t *sha1_checksum_ctx;
657
658   /* Final resting place of the checksum created by sha1_checksum_cxt. */
659   svn_checksum_t *sha1_checksum;
660
661   /* The length of the rep's contents (as fulltext, that is,
662      independent of how the rep actually stores the data.)  This is
663      retrieved when the baton is created, and used to determine when
664      we have read the last byte, at which point we compare checksums.
665
666      Getting this at baton creation time makes interleaved reads and
667      writes on the same rep in the same trail impossible.  But we're
668      not doing that, and probably no one ever should.  And anyway if
669      they do, they should see problems immediately. */
670   svn_filesize_t size;
671
672   /* Set to FALSE when the baton is created, TRUE when the checksum_ctx
673      is digestified. */
674   svn_boolean_t checksum_finalized;
675
676   /* Used for temporary allocations.  This pool is cleared at the
677      start of each invocation of the relevant stream read function --
678      see rep_read_contents().  */
679   apr_pool_t *scratch_pool;
680
681 };
682
683
684 static svn_error_t *
685 rep_read_get_baton(struct rep_read_baton **rb_p,
686                    svn_fs_t *fs,
687                    const char *rep_key,
688                    svn_boolean_t use_trail_for_reads,
689                    trail_t *trail,
690                    apr_pool_t *pool)
691 {
692   struct rep_read_baton *b;
693
694   b = apr_pcalloc(pool, sizeof(*b));
695   b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
696   b->sha1_checksum_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool);
697
698   if (rep_key)
699     SVN_ERR(svn_fs_base__rep_contents_size(&(b->size), fs, rep_key,
700                                            trail, pool));
701   else
702     b->size = 0;
703
704   b->checksum_finalized = FALSE;
705   b->fs = fs;
706   b->trail = use_trail_for_reads ? trail : NULL;
707   b->scratch_pool = svn_pool_create(pool);
708   b->rep_key = rep_key;
709   b->offset = 0;
710
711   *rb_p = b;
712
713   return SVN_NO_ERROR;
714 }
715
716
717 \f
718 /*** Retrieving data. ***/
719
720 svn_error_t *
721 svn_fs_base__rep_contents_size(svn_filesize_t *size_p,
722                                svn_fs_t *fs,
723                                const char *rep_key,
724                                trail_t *trail,
725                                apr_pool_t *pool)
726 {
727   representation_t *rep;
728
729   SVN_ERR(svn_fs_bdb__read_rep(&rep, fs, rep_key, trail, pool));
730
731   if (rep->kind == rep_kind_fulltext)
732     {
733       /* Get the size by asking Berkeley for the string's length. */
734       SVN_ERR(svn_fs_bdb__string_size(size_p, fs,
735                                       rep->contents.fulltext.string_key,
736                                       trail, pool));
737     }
738   else if (rep->kind == rep_kind_delta)
739     {
740       /* Get the size by finding the last window pkg in the delta and
741          adding its offset to its size.  This way, we won't even be
742          messed up by overlapping windows, as long as the window pkgs
743          are still ordered. */
744       apr_array_header_t *chunks = rep->contents.delta.chunks;
745       rep_delta_chunk_t *last_chunk;
746
747       SVN_ERR_ASSERT(chunks->nelts);
748
749       last_chunk = APR_ARRAY_IDX(chunks, chunks->nelts - 1,
750                                  rep_delta_chunk_t *);
751       *size_p = last_chunk->offset + last_chunk->size;
752     }
753   else /* unknown kind */
754     return UNKNOWN_NODE_KIND(rep_key);
755
756   return SVN_NO_ERROR;
757 }
758
759
760 svn_error_t *
761 svn_fs_base__rep_contents_checksums(svn_checksum_t **md5_checksum,
762                                     svn_checksum_t **sha1_checksum,
763                                     svn_fs_t *fs,
764                                     const char *rep_key,
765                                     trail_t *trail,
766                                     apr_pool_t *pool)
767 {
768   representation_t *rep;
769
770   SVN_ERR(svn_fs_bdb__read_rep(&rep, fs, rep_key, trail, pool));
771   if (md5_checksum)
772     *md5_checksum = svn_checksum_dup(rep->md5_checksum, pool);
773   if (sha1_checksum)
774     *sha1_checksum = svn_checksum_dup(rep->sha1_checksum, pool);
775
776   return SVN_NO_ERROR;
777 }
778
779
780 svn_error_t *
781 svn_fs_base__rep_contents(svn_string_t *str,
782                           svn_fs_t *fs,
783                           const char *rep_key,
784                           trail_t *trail,
785                           apr_pool_t *pool)
786 {
787   svn_filesize_t contents_size;
788   apr_size_t len;
789   char *data;
790
791   SVN_ERR(svn_fs_base__rep_contents_size(&contents_size, fs, rep_key,
792                                          trail, pool));
793
794   /* What if the contents are larger than we can handle? */
795   if (contents_size > SVN_MAX_OBJECT_SIZE)
796     return svn_error_createf
797       (SVN_ERR_FS_GENERAL, NULL,
798        _("Rep contents are too large: "
799          "got %s, limit is %s"),
800        apr_psprintf(pool, "%" SVN_FILESIZE_T_FMT, contents_size),
801        apr_psprintf(pool, "%" APR_SIZE_T_FMT, SVN_MAX_OBJECT_SIZE));
802   else
803     str->len = (apr_size_t) contents_size;
804
805   data = apr_palloc(pool, str->len);
806   str->data = data;
807   len = str->len;
808   SVN_ERR(rep_read_range(fs, rep_key, 0, data, &len, trail, pool));
809
810   /* Paranoia. */
811   if (len != str->len)
812     return svn_error_createf
813       (SVN_ERR_FS_CORRUPT, NULL,
814        _("Failure reading representation '%s'"), rep_key);
815
816   /* Just the standard paranoia. */
817   {
818     representation_t *rep;
819     svn_checksum_t *checksum, *rep_checksum;
820
821     SVN_ERR(svn_fs_bdb__read_rep(&rep, fs, rep_key, trail, pool));
822     rep_checksum = rep->sha1_checksum ? rep->sha1_checksum : rep->md5_checksum;
823     SVN_ERR(svn_checksum(&checksum, rep_checksum->kind, str->data, str->len,
824                          pool));
825
826     if (! svn_checksum_match(checksum, rep_checksum))
827       return svn_error_create(SVN_ERR_FS_CORRUPT,
828                 svn_checksum_mismatch_err(rep_checksum, checksum, pool,
829                             _("Checksum mismatch on representation '%s'"),
830                             rep_key),
831                 NULL);
832   }
833
834   return SVN_NO_ERROR;
835 }
836
837
838 struct read_rep_args
839 {
840   struct rep_read_baton *rb;   /* The data source.             */
841   char *buf;                   /* Where to put what we read.   */
842   apr_size_t *len;             /* How much to read / was read. */
843 };
844
845
846 /* BATON is of type `read_rep_args':
847
848    Read into BATON->rb->buf the *(BATON->len) bytes starting at
849    BATON->rb->offset from the data represented at BATON->rb->rep_key
850    in BATON->rb->fs, as part of TRAIL.
851
852    Afterwards, *(BATON->len) is the number of bytes actually read, and
853    BATON->rb->offset is incremented by that amount.
854
855    If BATON->rb->rep_key is null, this is assumed to mean the file's
856    contents have no representation, i.e., the file has no contents.
857    In that case, if BATON->rb->offset > 0, return the error
858    SVN_ERR_FS_FILE_CONTENTS_CHANGED, else just set *(BATON->len) to
859    zero and return.  */
860 static svn_error_t *
861 txn_body_read_rep(void *baton, trail_t *trail)
862 {
863   struct read_rep_args *args = baton;
864
865   if (args->rb->rep_key)
866     {
867       SVN_ERR(rep_read_range(args->rb->fs,
868                              args->rb->rep_key,
869                              args->rb->offset,
870                              args->buf,
871                              args->len,
872                              trail,
873                              args->rb->scratch_pool));
874
875       args->rb->offset += *(args->len);
876
877       /* We calculate the checksum just once, the moment we see the
878        * last byte of data.  But we can't assume there was a short
879        * read.  The caller may have known the length of the data and
880        * requested exactly that amount, so there would never be a
881        * short read.  (That's why the read baton has to know the
882        * length of the data in advance.)
883        *
884        * On the other hand, some callers invoke the stream reader in a
885        * loop whose termination condition is that the read returned
886        * zero bytes of data -- which usually results in the read
887        * function being called one more time *after* the call that got
888        * a short read (indicating end-of-stream).
889        *
890        * The conditions below ensure that we compare checksums even
891        * when there is no short read associated with the last byte of
892        * data, while also ensuring that it's harmless to repeatedly
893        * read 0 bytes from the stream.
894        */
895       if (! args->rb->checksum_finalized)
896         {
897           SVN_ERR(svn_checksum_update(args->rb->md5_checksum_ctx, args->buf,
898                                       *(args->len)));
899           SVN_ERR(svn_checksum_update(args->rb->sha1_checksum_ctx, args->buf,
900                                       *(args->len)));
901
902           if (args->rb->offset == args->rb->size)
903             {
904               representation_t *rep;
905
906               SVN_ERR(svn_checksum_final(&args->rb->md5_checksum,
907                                          args->rb->md5_checksum_ctx,
908                                          trail->pool));
909               SVN_ERR(svn_checksum_final(&args->rb->sha1_checksum,
910                                          args->rb->sha1_checksum_ctx,
911                                          trail->pool));
912               args->rb->checksum_finalized = TRUE;
913
914               SVN_ERR(svn_fs_bdb__read_rep(&rep, args->rb->fs,
915                                            args->rb->rep_key,
916                                            trail, trail->pool));
917
918               if (rep->md5_checksum
919                   && (! svn_checksum_match(rep->md5_checksum,
920                                            args->rb->md5_checksum)))
921                 return svn_error_create(SVN_ERR_FS_CORRUPT,
922                         svn_checksum_mismatch_err(rep->md5_checksum,
923                              args->rb->sha1_checksum, trail->pool,
924                              _("MD5 checksum mismatch on representation '%s'"),
925                              args->rb->rep_key),
926                         NULL);
927
928               if (rep->sha1_checksum
929                   && (! svn_checksum_match(rep->sha1_checksum,
930                                            args->rb->sha1_checksum)))
931                 return svn_error_createf(SVN_ERR_FS_CORRUPT,
932                         svn_checksum_mismatch_err(rep->sha1_checksum,
933                             args->rb->sha1_checksum, trail->pool,
934                             _("SHA1 checksum mismatch on representation '%s'"),
935                             args->rb->rep_key),
936                         NULL);
937             }
938         }
939     }
940   else if (args->rb->offset > 0)
941     {
942       return
943         svn_error_create
944         (SVN_ERR_FS_REP_CHANGED, NULL,
945          _("Null rep, but offset past zero already"));
946     }
947   else
948     *(args->len) = 0;
949
950   return SVN_NO_ERROR;
951 }
952
953
954 static svn_error_t *
955 rep_read_contents(void *baton, char *buf, apr_size_t *len)
956 {
957   struct rep_read_baton *rb = baton;
958   struct read_rep_args args;
959
960   /* Clear the scratch pool of the results of previous invocations. */
961   svn_pool_clear(rb->scratch_pool);
962
963   args.rb = rb;
964   args.buf = buf;
965   args.len = len;
966
967   /* If we got a trail, use it; else make one. */
968   if (rb->trail)
969     SVN_ERR(txn_body_read_rep(&args, rb->trail));
970   else
971     {
972       /* In the case of reading from the db, any returned data should
973          live in our pre-allocated buffer, so the whole operation can
974          happen within a single malloc/free cycle.  This prevents us
975          from creating millions of unnecessary trail subpools when
976          reading a big file.  */
977       SVN_ERR(svn_fs_base__retry_txn(rb->fs,
978                                      txn_body_read_rep,
979                                      &args,
980                                      TRUE,
981                                      rb->scratch_pool));
982     }
983   return SVN_NO_ERROR;
984 }
985
986
987 /** Writing. **/
988
989
990 struct rep_write_baton
991 {
992   /* The FS in which we're writing. */
993   svn_fs_t *fs;
994
995   /* The representation skel whose contents we want to write. */
996   const char *rep_key;
997
998   /* The transaction id under which this write action will take
999      place. */
1000   const char *txn_id;
1001
1002   /* If present, do the write as part of this trail, and use trail's
1003      pool.  Otherwise, see `pool' below.  */
1004   trail_t *trail;
1005
1006   /* SHA1 and MD5 checksums.  Initialized when the baton is created,
1007      updated as we write data, and finalized and stored when the
1008      stream is closed. */
1009   svn_checksum_ctx_t *md5_checksum_ctx;
1010   svn_checksum_t *md5_checksum;
1011   svn_checksum_ctx_t *sha1_checksum_ctx;
1012   svn_checksum_t *sha1_checksum;
1013   svn_boolean_t finalized;
1014
1015   /* Used for temporary allocations, iff `trail' (above) is null.  */
1016   apr_pool_t *pool;
1017
1018 };
1019
1020
1021 static struct rep_write_baton *
1022 rep_write_get_baton(svn_fs_t *fs,
1023                     const char *rep_key,
1024                     const char *txn_id,
1025                     trail_t *trail,
1026                     apr_pool_t *pool)
1027 {
1028   struct rep_write_baton *b;
1029
1030   b = apr_pcalloc(pool, sizeof(*b));
1031   b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
1032   b->sha1_checksum_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool);
1033   b->fs = fs;
1034   b->trail = trail;
1035   b->pool = pool;
1036   b->rep_key = rep_key;
1037   b->txn_id = txn_id;
1038   return b;
1039 }
1040
1041
1042
1043 /* Write LEN bytes from BUF into the end of the string represented via
1044    REP_KEY in FS, as part of TRAIL.  If the representation is not
1045    mutable, return the error SVN_FS_REP_NOT_MUTABLE. */
1046 static svn_error_t *
1047 rep_write(svn_fs_t *fs,
1048           const char *rep_key,
1049           const char *buf,
1050           apr_size_t len,
1051           const char *txn_id,
1052           trail_t *trail,
1053           apr_pool_t *pool)
1054 {
1055   representation_t *rep;
1056
1057   SVN_ERR(svn_fs_bdb__read_rep(&rep, fs, rep_key, trail, pool));
1058
1059   if (! rep_is_mutable(rep, txn_id))
1060     return svn_error_createf
1061       (SVN_ERR_FS_REP_NOT_MUTABLE, NULL,
1062        _("Rep '%s' is not mutable"), rep_key);
1063
1064   if (rep->kind == rep_kind_fulltext)
1065     {
1066       SVN_ERR(svn_fs_bdb__string_append
1067               (fs, &(rep->contents.fulltext.string_key), len, buf,
1068                trail, pool));
1069     }
1070   else if (rep->kind == rep_kind_delta)
1071     {
1072       /* There should never be a case when we have a mutable
1073          non-fulltext rep.  The only code that creates mutable reps is
1074          in this file, and it creates them fulltext. */
1075       return svn_error_createf
1076         (SVN_ERR_FS_CORRUPT, NULL,
1077          _("Rep '%s' both mutable and non-fulltext"), rep_key);
1078     }
1079   else /* unknown kind */
1080     return UNKNOWN_NODE_KIND(rep_key);
1081
1082   return SVN_NO_ERROR;
1083 }
1084
1085
1086 struct write_rep_args
1087 {
1088   struct rep_write_baton *wb;   /* Destination.       */
1089   const char *buf;              /* Data.              */
1090   apr_size_t len;               /* How much to write. */
1091 };
1092
1093
1094 /* BATON is of type `write_rep_args':
1095    Append onto BATON->wb->rep_key's contents BATON->len bytes of
1096    data from BATON->wb->buf, in BATON->rb->fs, as part of TRAIL.
1097
1098    If the representation is not mutable, return the error
1099    SVN_FS_REP_NOT_MUTABLE.  */
1100 static svn_error_t *
1101 txn_body_write_rep(void *baton, trail_t *trail)
1102 {
1103   struct write_rep_args *args = baton;
1104
1105   SVN_ERR(rep_write(args->wb->fs,
1106                     args->wb->rep_key,
1107                     args->buf,
1108                     args->len,
1109                     args->wb->txn_id,
1110                     trail,
1111                     trail->pool));
1112   SVN_ERR(svn_checksum_update(args->wb->md5_checksum_ctx,
1113                               args->buf, args->len));
1114   SVN_ERR(svn_checksum_update(args->wb->sha1_checksum_ctx,
1115                               args->buf, args->len));
1116   return SVN_NO_ERROR;
1117 }
1118
1119
1120 static svn_error_t *
1121 rep_write_contents(void *baton,
1122                    const char *buf,
1123                    apr_size_t *len)
1124 {
1125   struct rep_write_baton *wb = baton;
1126   struct write_rep_args args;
1127
1128   /* We toss LEN's indirectness because if not all the bytes are
1129      written, it's an error, so we wouldn't be reporting anything back
1130      through *LEN anyway. */
1131   args.wb = wb;
1132   args.buf = buf;
1133   args.len = *len;
1134
1135   /* If we got a trail, use it; else make one. */
1136   if (wb->trail)
1137     SVN_ERR(txn_body_write_rep(&args, wb->trail));
1138   else
1139     {
1140       /* In the case of simply writing the rep to the db, we're
1141          *certain* that there's no data coming back to us that needs
1142          to be preserved... so the whole operation can happen within a
1143          single malloc/free cycle.  This prevents us from creating
1144          millions of unnecessary trail subpools when writing a big
1145          file. */
1146       SVN_ERR(svn_fs_base__retry_txn(wb->fs,
1147                                      txn_body_write_rep,
1148                                      &args,
1149                                      TRUE,
1150                                      wb->pool));
1151     }
1152
1153   return SVN_NO_ERROR;
1154 }
1155
1156
1157 /* Helper for rep_write_close_contents(); see that doc string for
1158    more.  BATON is of type `struct rep_write_baton'. */
1159 static svn_error_t *
1160 txn_body_write_close_rep(void *baton, trail_t *trail)
1161 {
1162   struct rep_write_baton *wb = baton;
1163   representation_t *rep;
1164
1165   SVN_ERR(svn_fs_bdb__read_rep(&rep, wb->fs, wb->rep_key,
1166                                trail, trail->pool));
1167   rep->md5_checksum = svn_checksum_dup(wb->md5_checksum, trail->pool);
1168   rep->sha1_checksum = svn_checksum_dup(wb->sha1_checksum, trail->pool);
1169   return svn_fs_bdb__write_rep(wb->fs, wb->rep_key, rep,
1170                                trail, trail->pool);
1171 }
1172
1173
1174 /* BATON is of type `struct rep_write_baton'.
1175  *
1176  * Finalize BATON->md5_context and store the resulting digest under
1177  * BATON->rep_key.
1178  */
1179 static svn_error_t *
1180 rep_write_close_contents(void *baton)
1181 {
1182   struct rep_write_baton *wb = baton;
1183
1184   /* ### Thought: if we fixed apr-util MD5 contexts to allow repeated
1185      digestification, then we wouldn't need a stream close function at
1186      all -- instead, we could update the stored checksum each time a
1187      write occurred, which would have the added advantage of making
1188      interleaving reads and writes work.  Currently, they'd fail with
1189      a checksum mismatch, it just happens that our code never tries to
1190      do that anyway. */
1191
1192   if (! wb->finalized)
1193     {
1194       SVN_ERR(svn_checksum_final(&wb->md5_checksum, wb->md5_checksum_ctx,
1195                                  wb->pool));
1196       SVN_ERR(svn_checksum_final(&wb->sha1_checksum, wb->sha1_checksum_ctx,
1197                                  wb->pool));
1198       wb->finalized = TRUE;
1199     }
1200
1201   /* If we got a trail, use it; else make one. */
1202   if (wb->trail)
1203     return txn_body_write_close_rep(wb, wb->trail);
1204   else
1205     /* We need to keep our trail pool around this time so the
1206        checksums we've calculated survive. */
1207     return svn_fs_base__retry_txn(wb->fs, txn_body_write_close_rep,
1208                                   wb, FALSE, wb->pool);
1209 }
1210
1211
1212 /** Public read and write stream constructors. **/
1213
1214 svn_error_t *
1215 svn_fs_base__rep_contents_read_stream(svn_stream_t **rs_p,
1216                                       svn_fs_t *fs,
1217                                       const char *rep_key,
1218                                       svn_boolean_t use_trail_for_reads,
1219                                       trail_t *trail,
1220                                       apr_pool_t *pool)
1221 {
1222   struct rep_read_baton *rb;
1223
1224   SVN_ERR(rep_read_get_baton(&rb, fs, rep_key, use_trail_for_reads,
1225                              trail, pool));
1226   *rs_p = svn_stream_create(rb, pool);
1227   svn_stream_set_read(*rs_p, rep_read_contents);
1228
1229   return SVN_NO_ERROR;
1230 }
1231
1232
1233 /* Clear the contents of REP_KEY, so that it represents the empty
1234    string, as part of TRAIL.  TXN_ID is the id of the Subversion
1235    transaction under which this occurs.  If REP_KEY is not mutable,
1236    return the error SVN_ERR_FS_REP_NOT_MUTABLE.  */
1237 static svn_error_t *
1238 rep_contents_clear(svn_fs_t *fs,
1239                    const char *rep_key,
1240                    const char *txn_id,
1241                    trail_t *trail,
1242                    apr_pool_t *pool)
1243 {
1244   representation_t *rep;
1245   const char *str_key;
1246
1247   SVN_ERR(svn_fs_bdb__read_rep(&rep, fs, rep_key, trail, pool));
1248
1249   /* Make sure it's mutable. */
1250   if (! rep_is_mutable(rep, txn_id))
1251     return svn_error_createf
1252       (SVN_ERR_FS_REP_NOT_MUTABLE, NULL,
1253        _("Rep '%s' is not mutable"), rep_key);
1254
1255   SVN_ERR_ASSERT(rep->kind == rep_kind_fulltext);
1256
1257   /* If rep has no string, just return success.  Else, clear the
1258      underlying string.  */
1259   str_key = rep->contents.fulltext.string_key;
1260   if (str_key && *str_key)
1261     {
1262       SVN_ERR(svn_fs_bdb__string_clear(fs, str_key, trail, pool));
1263       rep->md5_checksum = NULL;
1264       rep->sha1_checksum = NULL;
1265       SVN_ERR(svn_fs_bdb__write_rep(fs, rep_key, rep, trail, pool));
1266     }
1267   return SVN_NO_ERROR;
1268 }
1269
1270
1271 svn_error_t *
1272 svn_fs_base__rep_contents_write_stream(svn_stream_t **ws_p,
1273                                        svn_fs_t *fs,
1274                                        const char *rep_key,
1275                                        const char *txn_id,
1276                                        svn_boolean_t use_trail_for_writes,
1277                                        trail_t *trail,
1278                                        apr_pool_t *pool)
1279 {
1280   struct rep_write_baton *wb;
1281
1282   /* Clear the current rep contents (free mutability check!). */
1283   SVN_ERR(rep_contents_clear(fs, rep_key, txn_id, trail, pool));
1284
1285   /* Now, generate the write baton and stream. */
1286   wb = rep_write_get_baton(fs, rep_key, txn_id,
1287                            use_trail_for_writes ? trail : NULL, pool);
1288   *ws_p = svn_stream_create(wb, pool);
1289   svn_stream_set_write(*ws_p, rep_write_contents);
1290   svn_stream_set_close(*ws_p, rep_write_close_contents);
1291
1292   return SVN_NO_ERROR;
1293 }
1294
1295
1296 \f
1297 /*** Deltified storage. ***/
1298
1299 /* Baton for svn_write_fn_t write_string_set(). */
1300 struct write_svndiff_strings_baton
1301 {
1302   /* The fs where lives the string we're writing. */
1303   svn_fs_t *fs;
1304
1305   /* The key of the string we're writing to.  Typically this is
1306      initialized to NULL, so svn_fs_base__string_append() can fill in a
1307      value. */
1308   const char *key;
1309
1310   /* The amount of txdelta data written to the current
1311      string-in-progress. */
1312   apr_size_t size;
1313
1314   /* The amount of svndiff header information we've written thus far
1315      to the strings table. */
1316   apr_size_t header_read;
1317
1318   /* The version number of the svndiff data written.  ### You'd better
1319      not count on this being populated after the first chunk is sent
1320      through the interface, since it lives at the 4th byte of the
1321      stream. */
1322   apr_byte_t version;
1323
1324   /* The trail we're writing in. */
1325   trail_t *trail;
1326
1327 };
1328
1329
1330 /* Function of type `svn_write_fn_t', for writing to a collection of
1331    strings; BATON is `struct write_svndiff_strings_baton *'.
1332
1333    On the first call, BATON->key is null.  A new string key in
1334    BATON->fs is chosen and stored in BATON->key; each call appends
1335    *LEN bytes from DATA onto the string.  *LEN is never changed; if
1336    the write fails to write all *LEN bytes, an error is returned.
1337    BATON->size is used to track the total amount of data written via
1338    this handler, and must be reset by the caller to 0 when appropriate.  */
1339 static svn_error_t *
1340 write_svndiff_strings(void *baton, const char *data, apr_size_t *len)
1341 {
1342   struct write_svndiff_strings_baton *wb = baton;
1343   const char *buf = data;
1344   apr_size_t nheader = 0;
1345
1346   /* If we haven't stripped all the header information from this
1347      stream yet, keep stripping.  If someone sends a first window
1348      through here that's shorter than 4 bytes long, this will probably
1349      cause a nuclear reactor meltdown somewhere in the American
1350      midwest.  */
1351   if (wb->header_read < 4)
1352     {
1353       nheader = 4 - wb->header_read;
1354       *len -= nheader;
1355       buf += nheader;
1356       wb->header_read += nheader;
1357
1358       /* If we have *now* read the full 4-byte header, check that
1359          least byte for the version number of the svndiff format. */
1360       if (wb->header_read == 4)
1361         wb->version = *(buf - 1);
1362     }
1363
1364   /* Append to the current string we're writing (or create a new one
1365      if WB->key is NULL). */
1366   SVN_ERR(svn_fs_bdb__string_append(wb->fs, &(wb->key), *len,
1367                                     buf, wb->trail, wb->trail->pool));
1368
1369   /* Make sure we (still) have a key. */
1370   if (wb->key == NULL)
1371     return svn_error_create(SVN_ERR_FS_GENERAL, NULL,
1372                             _("Failed to get new string key"));
1373
1374   /* Restore *LEN to the value it *would* have been were it not for
1375      header stripping. */
1376   *len += nheader;
1377
1378   /* Increment our running total of bytes written to this string. */
1379   wb->size += *len;
1380
1381   return SVN_NO_ERROR;
1382 }
1383
1384
1385 typedef struct window_write_t
1386 {
1387   const char *key; /* string key for this window */
1388   apr_size_t svndiff_len; /* amount of svndiff data written to the string */
1389   svn_filesize_t text_off; /* offset of fulltext represented by this window */
1390   apr_size_t text_len; /* amount of fulltext data represented by this window */
1391
1392 } window_write_t;
1393
1394
1395 svn_error_t *
1396 svn_fs_base__rep_deltify(svn_fs_t *fs,
1397                          const char *target,
1398                          const char *source,
1399                          trail_t *trail,
1400                          apr_pool_t *pool)
1401 {
1402   base_fs_data_t *bfd = fs->fsap_data;
1403   svn_stream_t *source_stream; /* stream to read the source */
1404   svn_stream_t *target_stream; /* stream to read the target */
1405   svn_txdelta_stream_t *txdelta_stream; /* stream to read delta windows  */
1406
1407   /* window-y things, and an array to track them */
1408   window_write_t *ww;
1409   apr_array_header_t *windows;
1410
1411   /* stream to write new (deltified) target data and its baton */
1412   svn_stream_t *new_target_stream;
1413   struct write_svndiff_strings_baton new_target_baton;
1414
1415   /* window handler/baton for writing to above stream */
1416   svn_txdelta_window_handler_t new_target_handler;
1417   void *new_target_handler_baton;
1418
1419   /* yes, we do windows */
1420   svn_txdelta_window_t *window;
1421
1422   /* The current offset into the fulltext that our window is about to
1423      write.  This doubles, after all windows are written, as the
1424      total size of the svndiff data for the deltification process. */
1425   svn_filesize_t tview_off = 0;
1426
1427   /* The total amount of diff data written while deltifying. */
1428   svn_filesize_t diffsize = 0;
1429
1430   /* TARGET's original string keys */
1431   apr_array_header_t *orig_str_keys;
1432
1433   /* The checksums for the representation's fulltext contents. */
1434   svn_checksum_t *rep_md5_checksum;
1435   svn_checksum_t *rep_sha1_checksum;
1436
1437   /* MD5 digest */
1438   const unsigned char *digest;
1439
1440   /* pool for holding the windows */
1441   apr_pool_t *wpool;
1442
1443   /* Paranoia: never allow a rep to be deltified against itself,
1444      because then there would be no fulltext reachable in the delta
1445      chain, and badness would ensue.  */
1446   if (strcmp(target, source) == 0)
1447     return svn_error_createf
1448       (SVN_ERR_FS_CORRUPT, NULL,
1449        _("Attempt to deltify '%s' against itself"),
1450        target);
1451
1452   /* Set up a handler for the svndiff data, which will write each
1453      window to its own string in the `strings' table. */
1454   new_target_baton.fs = fs;
1455   new_target_baton.trail = trail;
1456   new_target_baton.header_read = FALSE;
1457   new_target_stream = svn_stream_create(&new_target_baton, pool);
1458   svn_stream_set_write(new_target_stream, write_svndiff_strings);
1459
1460   /* Get streams to our source and target text data. */
1461   SVN_ERR(svn_fs_base__rep_contents_read_stream(&source_stream, fs, source,
1462                                                 TRUE, trail, pool));
1463   SVN_ERR(svn_fs_base__rep_contents_read_stream(&target_stream, fs, target,
1464                                                 TRUE, trail, pool));
1465
1466   /* Setup a stream to convert the textdelta data into svndiff windows. */
1467   svn_txdelta2(&txdelta_stream, source_stream, target_stream, TRUE, pool);
1468
1469   if (bfd->format >= SVN_FS_BASE__MIN_SVNDIFF1_FORMAT)
1470     svn_txdelta_to_svndiff3(&new_target_handler, &new_target_handler_baton,
1471                             new_target_stream, 1,
1472                             SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
1473   else
1474     svn_txdelta_to_svndiff3(&new_target_handler, &new_target_handler_baton,
1475                             new_target_stream, 0,
1476                             SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
1477
1478   /* subpool for the windows */
1479   wpool = svn_pool_create(pool);
1480
1481   /* Now, loop, manufacturing and dispatching windows of svndiff data. */
1482   windows = apr_array_make(pool, 1, sizeof(ww));
1483   do
1484     {
1485       /* Reset some baton variables. */
1486       new_target_baton.size = 0;
1487       new_target_baton.key = NULL;
1488
1489       /* Free the window. */
1490       svn_pool_clear(wpool);
1491
1492       /* Fetch the next window of txdelta data. */
1493       SVN_ERR(svn_txdelta_next_window(&window, txdelta_stream, wpool));
1494
1495       /* Send off this package to be written as svndiff data. */
1496       SVN_ERR(new_target_handler(window, new_target_handler_baton));
1497       if (window)
1498         {
1499           /* Add a new window description to our array. */
1500           ww = apr_pcalloc(pool, sizeof(*ww));
1501           ww->key = new_target_baton.key;
1502           ww->svndiff_len = new_target_baton.size;
1503           ww->text_off = tview_off;
1504           ww->text_len = window->tview_len;
1505           APR_ARRAY_PUSH(windows, window_write_t *) = ww;
1506
1507           /* Update our recordkeeping variables. */
1508           tview_off += window->tview_len;
1509           diffsize += ww->svndiff_len;
1510         }
1511
1512     } while (window);
1513
1514   svn_pool_destroy(wpool);
1515
1516   /* Having processed all the windows, we can query the MD5 digest
1517      from the stream.  */
1518   digest = svn_txdelta_md5_digest(txdelta_stream);
1519   if (! digest)
1520     return svn_error_createf
1521       (SVN_ERR_DELTA_MD5_CHECKSUM_ABSENT, NULL,
1522        _("Failed to calculate MD5 digest for '%s'"),
1523        source);
1524
1525   /* Construct a list of the strings used by the old representation so
1526      that we can delete them later.  While we are here, if the old
1527      representation was a fulltext, check to make sure the delta we're
1528      replacing it with is actually smaller.  (Don't perform this check
1529      if we're replacing a delta; in that case, we're going for a time
1530      optimization, not a space optimization.)  */
1531   {
1532     representation_t *old_rep;
1533     const char *str_key;
1534
1535     SVN_ERR(svn_fs_bdb__read_rep(&old_rep, fs, target, trail, pool));
1536     if (old_rep->kind == rep_kind_fulltext)
1537       {
1538         svn_filesize_t old_size = 0;
1539
1540         str_key = old_rep->contents.fulltext.string_key;
1541         SVN_ERR(svn_fs_bdb__string_size(&old_size, fs, str_key,
1542                                         trail, pool));
1543         orig_str_keys = apr_array_make(pool, 1, sizeof(str_key));
1544         APR_ARRAY_PUSH(orig_str_keys, const char *) = str_key;
1545
1546         /* If the new data is NOT an space optimization, destroy the
1547            string(s) we created, and get outta here. */
1548         if (diffsize >= old_size)
1549           {
1550             int i;
1551             for (i = 0; i < windows->nelts; i++)
1552               {
1553                 ww = APR_ARRAY_IDX(windows, i, window_write_t *);
1554                 SVN_ERR(svn_fs_bdb__string_delete(fs, ww->key, trail, pool));
1555               }
1556             return SVN_NO_ERROR;
1557           }
1558       }
1559     else if (old_rep->kind == rep_kind_delta)
1560       SVN_ERR(delta_string_keys(&orig_str_keys, old_rep, pool));
1561     else /* unknown kind */
1562       return UNKNOWN_NODE_KIND(target);
1563
1564     /* Save the checksums, since the new rep needs them. */
1565     rep_md5_checksum = svn_checksum_dup(old_rep->md5_checksum, pool);
1566     rep_sha1_checksum = svn_checksum_dup(old_rep->sha1_checksum, pool);
1567   }
1568
1569   /* Hook the new strings we wrote into the rest of the filesystem by
1570      building a new representation to replace our old one. */
1571   {
1572     representation_t new_rep;
1573     rep_delta_chunk_t *chunk;
1574     apr_array_header_t *chunks;
1575     int i;
1576
1577     new_rep.kind = rep_kind_delta;
1578     new_rep.txn_id = NULL;
1579
1580     /* Migrate the old rep's checksums to the new rep. */
1581     new_rep.md5_checksum = svn_checksum_dup(rep_md5_checksum, pool);
1582     new_rep.sha1_checksum = svn_checksum_dup(rep_sha1_checksum, pool);
1583
1584     chunks = apr_array_make(pool, windows->nelts, sizeof(chunk));
1585
1586     /* Loop through the windows we wrote, creating and adding new
1587        chunks to the representation. */
1588     for (i = 0; i < windows->nelts; i++)
1589       {
1590         ww = APR_ARRAY_IDX(windows, i, window_write_t *);
1591
1592         /* Allocate a chunk and its window */
1593         chunk = apr_palloc(pool, sizeof(*chunk));
1594         chunk->offset = ww->text_off;
1595
1596         /* Populate the window */
1597         chunk->version = new_target_baton.version;
1598         chunk->string_key = ww->key;
1599         chunk->size = ww->text_len;
1600         chunk->rep_key = source;
1601
1602         /* Add this chunk to the array. */
1603         APR_ARRAY_PUSH(chunks, rep_delta_chunk_t *) = chunk;
1604       }
1605
1606     /* Put the chunks array into the representation. */
1607     new_rep.contents.delta.chunks = chunks;
1608
1609     /* Write out the new representation. */
1610     SVN_ERR(svn_fs_bdb__write_rep(fs, target, &new_rep, trail, pool));
1611
1612     /* Delete the original pre-deltified strings. */
1613     SVN_ERR(delete_strings(orig_str_keys, fs, trail, pool));
1614   }
1615
1616   return SVN_NO_ERROR;
1617 }