1/* low_level.c --- low level r/w access to fs_fs file structures
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 "svn_private_config.h"
24#include "svn_hash.h"
25#include "svn_pools.h"
26#include "svn_sorts.h"
27#include "private/svn_sorts_private.h"
28#include "private/svn_string_private.h"
29#include "private/svn_subr_private.h"
30#include "private/svn_fspath.h"
31
32#include "../libsvn_fs/fs-loader.h"
33
34#include "low_level.h"
35
36/* Headers used to describe node-revision in the revision file. */
37#define HEADER_ID          "id"
38#define HEADER_TYPE        "type"
39#define HEADER_COUNT       "count"
40#define HEADER_PROPS       "props"
41#define HEADER_TEXT        "text"
42#define HEADER_CPATH       "cpath"
43#define HEADER_PRED        "pred"
44#define HEADER_COPYFROM    "copyfrom"
45#define HEADER_COPYROOT    "copyroot"
46#define HEADER_FRESHTXNRT  "is-fresh-txn-root"
47#define HEADER_MINFO_HERE  "minfo-here"
48#define HEADER_MINFO_CNT   "minfo-cnt"
49
50/* Kinds that a change can be. */
51#define ACTION_MODIFY      "modify"
52#define ACTION_ADD         "add"
53#define ACTION_DELETE      "delete"
54#define ACTION_REPLACE     "replace"
55#define ACTION_RESET       "reset"
56
57/* True and False flags. */
58#define FLAG_TRUE          "true"
59#define FLAG_FALSE         "false"
60
61/* Kinds of representation. */
62#define REP_PLAIN          "PLAIN"
63#define REP_DELTA          "DELTA"
64
65/* An arbitrary maximum path length, so clients can't run us out of memory
66 * by giving us arbitrarily large paths. */
67#define FSFS_MAX_PATH_LEN 4096
68
69/* The 256 is an arbitrary size large enough to hold the node id and the
70 * various flags. */
71#define MAX_CHANGE_LINE_LEN FSFS_MAX_PATH_LEN + 256
72
73/* Convert the C string in *TEXT to a revision number and return it in *REV.
74 * Overflows, negative values other than -1 and terminating characters other
75 * than 0x20 or 0x0 will cause an error.  Set *TEXT to the first char after
76 * the initial separator or to EOS.
77 */
78static svn_error_t *
79parse_revnum(svn_revnum_t *rev,
80             const char **text)
81{
82  const char *string = *text;
83  if ((string[0] == '-') && (string[1] == '1'))
84    {
85      *rev = SVN_INVALID_REVNUM;
86      string += 2;
87    }
88  else
89    {
90      SVN_ERR(svn_revnum_parse(rev, string, &string));
91    }
92
93  if (*string == ' ')
94    ++string;
95  else if (*string != '\0')
96    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
97                            _("Invalid character in revision number"));
98
99  *text = string;
100  return SVN_NO_ERROR;
101}
102
103svn_error_t *
104svn_fs_fs__parse_revision_trailer(apr_off_t *root_offset,
105                                  apr_off_t *changes_offset,
106                                  svn_stringbuf_t *trailer,
107                                  svn_revnum_t rev)
108{
109  int i, num_bytes;
110  const char *str;
111
112  /* This cast should be safe since the maximum amount read, 64, will
113     never be bigger than the size of an int. */
114  num_bytes = (int) trailer->len;
115
116  /* The last byte should be a newline. */
117  if (trailer->len == 0 || trailer->data[trailer->len - 1] != '\n')
118    {
119      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
120                               _("Revision file (r%ld) lacks trailing newline"),
121                               rev);
122    }
123
124  /* Look for the next previous newline. */
125  for (i = num_bytes - 2; i >= 0; i--)
126    {
127      if (trailer->data[i] == '\n')
128        break;
129    }
130
131  if (i < 0)
132    {
133      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
134                               _("Final line in revision file (r%ld) longer "
135                                 "than 64 characters"),
136                               rev);
137    }
138
139  i++;
140  str = &trailer->data[i];
141
142  /* find the next space */
143  for ( ; i < (num_bytes - 2) ; i++)
144    if (trailer->data[i] == ' ')
145      break;
146
147  if (i == (num_bytes - 2))
148    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
149                             _("Final line in revision file r%ld missing space"),
150                             rev);
151
152  if (root_offset)
153    {
154      apr_int64_t val;
155
156      trailer->data[i] = '\0';
157      SVN_ERR(svn_cstring_atoi64(&val, str));
158      *root_offset = (apr_off_t)val;
159    }
160
161  i++;
162  str = &trailer->data[i];
163
164  /* find the next newline */
165  for ( ; i < num_bytes; i++)
166    if (trailer->data[i] == '\n')
167      break;
168
169  if (changes_offset)
170    {
171      apr_int64_t val;
172
173      trailer->data[i] = '\0';
174      SVN_ERR(svn_cstring_atoi64(&val, str));
175      *changes_offset = (apr_off_t)val;
176    }
177
178  return SVN_NO_ERROR;
179}
180
181svn_stringbuf_t *
182svn_fs_fs__unparse_revision_trailer(apr_off_t root_offset,
183                                    apr_off_t changes_offset,
184                                    apr_pool_t *result_pool)
185{
186  return svn_stringbuf_createf(result_pool,
187                               "%" APR_OFF_T_FMT " %" APR_OFF_T_FMT "\n",
188                               root_offset,
189                               changes_offset);
190}
191
192/* If ERR is not NULL, wrap it MESSAGE.  The latter must have an %ld
193 * format parameter that will be filled with REV. */
194static svn_error_t *
195wrap_footer_error(svn_error_t *err,
196                  const char *message,
197                  svn_revnum_t rev)
198{
199  if (err)
200    return svn_error_quick_wrapf(err, message, rev);
201
202  return SVN_NO_ERROR;
203}
204
205svn_error_t *
206svn_fs_fs__parse_footer(apr_off_t *l2p_offset,
207                        svn_checksum_t **l2p_checksum,
208                        apr_off_t *p2l_offset,
209                        svn_checksum_t **p2l_checksum,
210                        svn_stringbuf_t *footer,
211                        svn_revnum_t rev,
212                        apr_off_t footer_offset,
213                        apr_pool_t *result_pool)
214{
215  apr_int64_t val;
216  char *last_str = footer->data;
217
218  /* Get the L2P offset. */
219  const char *str = svn_cstring_tokenize(" ", &last_str);
220  if (str == NULL)
221    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
222                             "Invalid r%ld footer", rev);
223
224  SVN_ERR(wrap_footer_error(svn_cstring_strtoi64(&val, str, 0,
225                                                 footer_offset - 1, 10),
226                            "Invalid L2P offset in r%ld footer",
227                            rev));
228  *l2p_offset = (apr_off_t)val;
229
230  /* Get the L2P checksum. */
231  str = svn_cstring_tokenize(" ", &last_str);
232  if (str == NULL)
233    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
234                             "Invalid r%ld footer", rev);
235
236  SVN_ERR(svn_checksum_parse_hex(l2p_checksum, svn_checksum_md5, str,
237                                 result_pool));
238
239  /* Get the P2L offset. */
240  str = svn_cstring_tokenize(" ", &last_str);
241  if (str == NULL)
242    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
243                             "Invalid r%ld footer", rev);
244
245  SVN_ERR(wrap_footer_error(svn_cstring_strtoi64(&val, str, 0,
246                                                 footer_offset - 1, 10),
247                            "Invalid P2L offset in r%ld footer",
248                            rev));
249  *p2l_offset = (apr_off_t)val;
250
251  /* The P2L indes follows the L2P index */
252  if (*p2l_offset <= *l2p_offset)
253    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
254                             "P2L offset %s must be larger than L2P offset %s"
255                             " in r%ld footer",
256                             apr_psprintf(result_pool,
257                                          "0x%" APR_UINT64_T_HEX_FMT,
258                                          (apr_uint64_t)*p2l_offset),
259                             apr_psprintf(result_pool,
260                                          "0x%" APR_UINT64_T_HEX_FMT,
261                                          (apr_uint64_t)*l2p_offset),
262                             rev);
263
264  /* Get the P2L checksum. */
265  str = svn_cstring_tokenize(" ", &last_str);
266  if (str == NULL)
267    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
268                             "Invalid r%ld footer", rev);
269
270  SVN_ERR(svn_checksum_parse_hex(p2l_checksum, svn_checksum_md5, str,
271                                 result_pool));
272
273  return SVN_NO_ERROR;
274}
275
276svn_stringbuf_t *
277svn_fs_fs__unparse_footer(apr_off_t l2p_offset,
278                          svn_checksum_t *l2p_checksum,
279                          apr_off_t p2l_offset,
280                          svn_checksum_t *p2l_checksum,
281                          apr_pool_t *result_pool,
282                          apr_pool_t *scratch_pool)
283{
284  return svn_stringbuf_createf(result_pool,
285                               "%" APR_OFF_T_FMT " %s %" APR_OFF_T_FMT " %s",
286                               l2p_offset,
287                               svn_checksum_to_cstring(l2p_checksum,
288                                                       scratch_pool),
289                               p2l_offset,
290                               svn_checksum_to_cstring(p2l_checksum,
291                                                       scratch_pool));
292}
293
294/* Read the next entry in the changes record from file FILE and store
295   the resulting change in *CHANGE_P.  If there is no next record,
296   store NULL there.  Perform all allocations from POOL. */
297static svn_error_t *
298read_change(change_t **change_p,
299            svn_stream_t *stream,
300            apr_pool_t *result_pool,
301            apr_pool_t *scratch_pool)
302{
303  svn_stringbuf_t *line;
304  svn_boolean_t eof = TRUE;
305  change_t *change;
306  char *str, *last_str, *kind_str;
307  svn_fs_path_change2_t *info;
308
309  /* Default return value. */
310  *change_p = NULL;
311
312  SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, scratch_pool));
313
314  /* Check for a blank line. */
315  if (eof || (line->len == 0))
316    return SVN_NO_ERROR;
317
318  change = apr_pcalloc(result_pool, sizeof(*change));
319  info = &change->info;
320  last_str = line->data;
321
322  /* Get the node-id of the change. */
323  str = svn_cstring_tokenize(" ", &last_str);
324  if (str == NULL)
325    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
326                            _("Invalid changes line in rev-file"));
327
328  SVN_ERR(svn_fs_fs__id_parse(&info->node_rev_id, str, result_pool));
329  if (info->node_rev_id == NULL)
330    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
331                            _("Invalid changes line in rev-file"));
332
333  /* Get the change type. */
334  str = svn_cstring_tokenize(" ", &last_str);
335  if (str == NULL)
336    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
337                            _("Invalid changes line in rev-file"));
338
339  /* Don't bother to check the format number before looking for
340   * node-kinds: just read them if you find them. */
341  info->node_kind = svn_node_unknown;
342  kind_str = strchr(str, '-');
343  if (kind_str)
344    {
345      /* Cap off the end of "str" (the action). */
346      *kind_str = '\0';
347      kind_str++;
348      if (strcmp(kind_str, SVN_FS_FS__KIND_FILE) == 0)
349        info->node_kind = svn_node_file;
350      else if (strcmp(kind_str, SVN_FS_FS__KIND_DIR) == 0)
351        info->node_kind = svn_node_dir;
352      else
353        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
354                                _("Invalid changes line in rev-file"));
355    }
356
357  if (strcmp(str, ACTION_MODIFY) == 0)
358    {
359      info->change_kind = svn_fs_path_change_modify;
360    }
361  else if (strcmp(str, ACTION_ADD) == 0)
362    {
363      info->change_kind = svn_fs_path_change_add;
364    }
365  else if (strcmp(str, ACTION_DELETE) == 0)
366    {
367      info->change_kind = svn_fs_path_change_delete;
368    }
369  else if (strcmp(str, ACTION_REPLACE) == 0)
370    {
371      info->change_kind = svn_fs_path_change_replace;
372    }
373  else if (strcmp(str, ACTION_RESET) == 0)
374    {
375      info->change_kind = svn_fs_path_change_reset;
376    }
377  else
378    {
379      return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
380                              _("Invalid change kind in rev file"));
381    }
382
383  /* Get the text-mod flag. */
384  str = svn_cstring_tokenize(" ", &last_str);
385  if (str == NULL)
386    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
387                            _("Invalid changes line in rev-file"));
388
389  if (strcmp(str, FLAG_TRUE) == 0)
390    {
391      info->text_mod = TRUE;
392    }
393  else if (strcmp(str, FLAG_FALSE) == 0)
394    {
395      info->text_mod = FALSE;
396    }
397  else
398    {
399      return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
400                              _("Invalid text-mod flag in rev-file"));
401    }
402
403  /* Get the prop-mod flag. */
404  str = svn_cstring_tokenize(" ", &last_str);
405  if (str == NULL)
406    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
407                            _("Invalid changes line in rev-file"));
408
409  if (strcmp(str, FLAG_TRUE) == 0)
410    {
411      info->prop_mod = TRUE;
412    }
413  else if (strcmp(str, FLAG_FALSE) == 0)
414    {
415      info->prop_mod = FALSE;
416    }
417  else
418    {
419      return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
420                              _("Invalid prop-mod flag in rev-file"));
421    }
422
423  /* Get the mergeinfo-mod flag if given.  Otherwise, the next thing
424     is the path starting with a slash.  Also, we must initialize the
425     flag explicitly because 0 is not valid for a svn_tristate_t. */
426  info->mergeinfo_mod = svn_tristate_unknown;
427  if (*last_str != '/')
428    {
429      str = svn_cstring_tokenize(" ", &last_str);
430      if (str == NULL)
431        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
432                                _("Invalid changes line in rev-file"));
433
434      if (strcmp(str, FLAG_TRUE) == 0)
435        {
436          info->mergeinfo_mod = svn_tristate_true;
437        }
438      else if (strcmp(str, FLAG_FALSE) == 0)
439        {
440          info->mergeinfo_mod = svn_tristate_false;
441        }
442      else
443        {
444          return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
445                              _("Invalid mergeinfo-mod flag in rev-file"));
446        }
447    }
448
449  /* Get the changed path. */
450  if (!svn_fspath__is_canonical(last_str))
451    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
452                            _("Invalid path in changes line"));
453
454  change->path.len = strlen(last_str);
455  change->path.data = apr_pstrdup(result_pool, last_str);
456
457  /* Read the next line, the copyfrom line. */
458  SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, scratch_pool));
459  info->copyfrom_known = TRUE;
460  if (eof || line->len == 0)
461    {
462      info->copyfrom_rev = SVN_INVALID_REVNUM;
463      info->copyfrom_path = NULL;
464    }
465  else
466    {
467      last_str = line->data;
468      SVN_ERR(parse_revnum(&info->copyfrom_rev, (const char **)&last_str));
469
470      if (!svn_fspath__is_canonical(last_str))
471        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
472                                _("Invalid copy-from path in changes line"));
473
474      info->copyfrom_path = apr_pstrdup(result_pool, last_str);
475    }
476
477  *change_p = change;
478
479  return SVN_NO_ERROR;
480}
481
482svn_error_t *
483svn_fs_fs__read_changes(apr_array_header_t **changes,
484                        svn_stream_t *stream,
485                        int max_count,
486                        apr_pool_t *result_pool,
487                        apr_pool_t *scratch_pool)
488{
489  apr_pool_t *iterpool;
490
491  /* Pre-allocate enough room for most change lists.
492     (will be auto-expanded as necessary).
493
494     Chose the default to just below 2^N such that the doubling reallocs
495     will request roughly 2^M bytes from the OS without exceeding the
496     respective two-power by just a few bytes (leaves room array and APR
497     node overhead for large enough M).
498   */
499  *changes = apr_array_make(result_pool, 63, sizeof(change_t *));
500
501  iterpool = svn_pool_create(scratch_pool);
502  for (; max_count > 0; --max_count)
503    {
504      change_t *change;
505      svn_pool_clear(iterpool);
506      SVN_ERR(read_change(&change, stream, result_pool, iterpool));
507      if (!change)
508        break;
509
510      APR_ARRAY_PUSH(*changes, change_t*) = change;
511    }
512  svn_pool_destroy(iterpool);
513
514  return SVN_NO_ERROR;
515}
516
517svn_error_t *
518svn_fs_fs__read_changes_incrementally(svn_stream_t *stream,
519                                      svn_fs_fs__change_receiver_t
520                                        change_receiver,
521                                      void *change_receiver_baton,
522                                      apr_pool_t *scratch_pool)
523{
524  change_t *change;
525  apr_pool_t *iterpool;
526
527  iterpool = svn_pool_create(scratch_pool);
528  do
529    {
530      svn_pool_clear(iterpool);
531
532      SVN_ERR(read_change(&change, stream, iterpool, iterpool));
533      if (change)
534        SVN_ERR(change_receiver(change_receiver_baton, change, iterpool));
535    }
536  while (change);
537  svn_pool_destroy(iterpool);
538
539  return SVN_NO_ERROR;
540}
541
542/* Write a single change entry, path PATH, change CHANGE, to STREAM.
543
544   Only include the node kind field if INCLUDE_NODE_KIND is true.  Only
545   include the mergeinfo-mod field if INCLUDE_MERGEINFO_MODS is true.
546   All temporary allocations are in SCRATCH_POOL. */
547static svn_error_t *
548write_change_entry(svn_stream_t *stream,
549                   const char *path,
550                   svn_fs_path_change2_t *change,
551                   svn_boolean_t include_node_kind,
552                   svn_boolean_t include_mergeinfo_mods,
553                   apr_pool_t *scratch_pool)
554{
555  const char *idstr;
556  const char *change_string = NULL;
557  const char *kind_string = "";
558  const char *mergeinfo_string = "";
559  svn_stringbuf_t *buf;
560  apr_size_t len;
561
562  switch (change->change_kind)
563    {
564    case svn_fs_path_change_modify:
565      change_string = ACTION_MODIFY;
566      break;
567    case svn_fs_path_change_add:
568      change_string = ACTION_ADD;
569      break;
570    case svn_fs_path_change_delete:
571      change_string = ACTION_DELETE;
572      break;
573    case svn_fs_path_change_replace:
574      change_string = ACTION_REPLACE;
575      break;
576    case svn_fs_path_change_reset:
577      change_string = ACTION_RESET;
578      break;
579    default:
580      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
581                               _("Invalid change type %d"),
582                               change->change_kind);
583    }
584
585  if (change->node_rev_id)
586    idstr = svn_fs_fs__id_unparse(change->node_rev_id, scratch_pool)->data;
587  else
588    idstr = ACTION_RESET;
589
590  if (include_node_kind)
591    {
592      SVN_ERR_ASSERT(change->node_kind == svn_node_dir
593                     || change->node_kind == svn_node_file);
594      kind_string = apr_psprintf(scratch_pool, "-%s",
595                                 change->node_kind == svn_node_dir
596                                 ? SVN_FS_FS__KIND_DIR
597                                  : SVN_FS_FS__KIND_FILE);
598    }
599
600  if (include_mergeinfo_mods && change->mergeinfo_mod != svn_tristate_unknown)
601    mergeinfo_string = apr_psprintf(scratch_pool, " %s",
602                                    change->mergeinfo_mod == svn_tristate_true
603                                      ? FLAG_TRUE
604                                      : FLAG_FALSE);
605
606  buf = svn_stringbuf_createf(scratch_pool, "%s %s%s %s %s%s %s\n",
607                              idstr, change_string, kind_string,
608                              change->text_mod ? FLAG_TRUE : FLAG_FALSE,
609                              change->prop_mod ? FLAG_TRUE : FLAG_FALSE,
610                              mergeinfo_string,
611                              path);
612
613  if (SVN_IS_VALID_REVNUM(change->copyfrom_rev))
614    {
615      svn_stringbuf_appendcstr(buf, apr_psprintf(scratch_pool, "%ld %s",
616                                                 change->copyfrom_rev,
617                                                 change->copyfrom_path));
618    }
619
620   svn_stringbuf_appendbyte(buf, '\n');
621
622   /* Write all change info in one write call. */
623   len = buf->len;
624   return svn_error_trace(svn_stream_write(stream, buf->data, &len));
625}
626
627svn_error_t *
628svn_fs_fs__write_changes(svn_stream_t *stream,
629                         svn_fs_t *fs,
630                         apr_hash_t *changes,
631                         svn_boolean_t terminate_list,
632                         apr_pool_t *scratch_pool)
633{
634  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
635  fs_fs_data_t *ffd = fs->fsap_data;
636  svn_boolean_t include_node_kinds =
637      ffd->format >= SVN_FS_FS__MIN_KIND_IN_CHANGED_FORMAT;
638  svn_boolean_t include_mergeinfo_mods =
639      ffd->format >= SVN_FS_FS__MIN_MERGEINFO_IN_CHANGED_FORMAT;
640  apr_array_header_t *sorted_changed_paths;
641  int i;
642
643  /* For the sake of the repository administrator sort the changes so
644     that the final file is deterministic and repeatable, however the
645     rest of the FSFS code doesn't require any particular order here.
646
647     Also, this sorting is only effective in writing all entries with
648     a single call as write_final_changed_path_info() does.  For the
649     list being written incrementally during transaction, we actually
650     *must not* change the order of entries from different calls.
651   */
652  sorted_changed_paths = svn_sort__hash(changes,
653                                        svn_sort_compare_items_lexically,
654                                        scratch_pool);
655
656  /* Write all items to disk in the new order. */
657  for (i = 0; i < sorted_changed_paths->nelts; ++i)
658    {
659      svn_fs_path_change2_t *change;
660      const char *path;
661
662      svn_pool_clear(iterpool);
663
664      change = APR_ARRAY_IDX(sorted_changed_paths, i, svn_sort__item_t).value;
665      path = APR_ARRAY_IDX(sorted_changed_paths, i, svn_sort__item_t).key;
666
667      /* Write out the new entry into the final rev-file. */
668      SVN_ERR(write_change_entry(stream, path, change, include_node_kinds,
669                                 include_mergeinfo_mods, iterpool));
670    }
671
672  if (terminate_list)
673    SVN_ERR(svn_stream_puts(stream, "\n"));
674
675  svn_pool_destroy(iterpool);
676
677  return SVN_NO_ERROR;
678}
679
680/* Given a revision file FILE that has been pre-positioned at the
681   beginning of a Node-Rev header block, read in that header block and
682   store it in the apr_hash_t HEADERS.  All allocations will be from
683   RESULT_POOL. */
684static svn_error_t *
685read_header_block(apr_hash_t **headers,
686                  svn_stream_t *stream,
687                  apr_pool_t *result_pool)
688{
689  *headers = svn_hash__make(result_pool);
690
691  while (1)
692    {
693      svn_stringbuf_t *header_str;
694      const char *name, *value;
695      apr_size_t i = 0;
696      apr_size_t name_len;
697      svn_boolean_t eof;
698
699      SVN_ERR(svn_stream_readline(stream, &header_str, "\n", &eof,
700                                  result_pool));
701
702      if (eof || header_str->len == 0)
703        break; /* end of header block */
704
705      while (header_str->data[i] != ':')
706        {
707          if (header_str->data[i] == '\0')
708            return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
709                                     _("Found malformed header '%s' in "
710                                       "revision file"),
711                                     header_str->data);
712          i++;
713        }
714
715      /* Create a 'name' string and point to it. */
716      header_str->data[i] = '\0';
717      name = header_str->data;
718      name_len = i;
719
720      /* Check if we have enough data to parse. */
721      if (i + 2 > header_str->len)
722        {
723          /* Restore the original line for the error. */
724          header_str->data[i] = ':';
725          return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
726                                   _("Found malformed header '%s' in "
727                                     "revision file"),
728                                   header_str->data);
729        }
730
731      /* Skip over the NULL byte and the space following it. */
732      i += 2;
733
734      value = header_str->data + i;
735
736      /* header_str is safely in our pool, so we can use bits of it as
737         key and value. */
738      apr_hash_set(*headers, name, name_len, value);
739    }
740
741  return SVN_NO_ERROR;
742}
743
744/* ### Ouch!  The implementation of this function currently modifies
745   ### the input string when tokenizing it (so the input cannot be
746   ### used after that). */
747svn_error_t *
748svn_fs_fs__parse_representation(representation_t **rep_p,
749                                svn_stringbuf_t *text,
750                                apr_pool_t *result_pool,
751                                apr_pool_t *scratch_pool)
752{
753  representation_t *rep;
754  char *str;
755  apr_int64_t val;
756  char *string = text->data;
757  svn_checksum_t *checksum;
758  const char *end;
759
760  rep = apr_pcalloc(result_pool, sizeof(*rep));
761  *rep_p = rep;
762
763  SVN_ERR(parse_revnum(&rep->revision, (const char **)&string));
764
765  /* initialize transaction info (never stored) */
766  svn_fs_fs__id_txn_reset(&rep->txn_id);
767
768  /* while in transactions, it is legal to simply write "-1" */
769  str = svn_cstring_tokenize(" ", &string);
770  if (str == NULL)
771    {
772      if (rep->revision == SVN_INVALID_REVNUM)
773        return SVN_NO_ERROR;
774
775      return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
776                              _("Malformed text representation offset line in node-rev"));
777    }
778
779  SVN_ERR(svn_cstring_atoi64(&val, str));
780  rep->item_index = (apr_uint64_t)val;
781
782  str = svn_cstring_tokenize(" ", &string);
783  if (str == NULL)
784    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
785                            _("Malformed text representation offset line in node-rev"));
786
787  SVN_ERR(svn_cstring_atoi64(&val, str));
788  rep->size = (svn_filesize_t)val;
789
790  str = svn_cstring_tokenize(" ", &string);
791  if (str == NULL)
792    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
793                            _("Malformed text representation offset line in node-rev"));
794
795  SVN_ERR(svn_cstring_atoi64(&val, str));
796  rep->expanded_size = (svn_filesize_t)val;
797
798  /* Read in the MD5 hash. */
799  str = svn_cstring_tokenize(" ", &string);
800  if ((str == NULL) || (strlen(str) != (APR_MD5_DIGESTSIZE * 2)))
801    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
802                            _("Malformed text representation offset line in node-rev"));
803
804  SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_md5, str,
805                                 scratch_pool));
806
807  /* If STR is a all-zero checksum, CHECKSUM will be NULL and REP already
808     contains the correct value. */
809  if (checksum)
810    memcpy(rep->md5_digest, checksum->digest, sizeof(rep->md5_digest));
811
812  /* The remaining fields are only used for formats >= 4, so check that. */
813  str = svn_cstring_tokenize(" ", &string);
814  if (str == NULL)
815    return SVN_NO_ERROR;
816
817  /* Is the SHA1 hash present? */
818  if (str[0] == '-' && str[1] == 0)
819    {
820      checksum = NULL;
821    }
822  else
823    {
824      /* Read the SHA1 hash. */
825      if (strlen(str) != (APR_SHA1_DIGESTSIZE * 2))
826        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
827                                _("Malformed text representation offset line in node-rev"));
828
829      SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_sha1, str,
830                                     scratch_pool));
831    }
832
833  /* We do have a valid SHA1 but it might be all 0.
834     We cannot be sure where that came from (Alas! legacy), so let's not
835     claim we know the SHA1 in that case. */
836  rep->has_sha1 = checksum != NULL;
837
838  /* If STR is a all-zero checksum, CHECKSUM will be NULL and REP already
839     contains the correct value. */
840  if (checksum)
841    memcpy(rep->sha1_digest, checksum->digest, sizeof(rep->sha1_digest));
842
843  str = svn_cstring_tokenize(" ", &string);
844  if (str == NULL)
845    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
846                            _("Malformed text representation offset line in node-rev"));
847
848  /* Is the uniquifier present? */
849  if (str[0] == '-' && str[1] == 0)
850    {
851      end = string;
852    }
853  else
854    {
855      char *substring = str;
856
857      /* Read the uniquifier. */
858      str = svn_cstring_tokenize("/", &substring);
859      if (str == NULL)
860        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
861                                _("Malformed text representation offset line in node-rev"));
862
863      SVN_ERR(svn_fs_fs__id_txn_parse(&rep->uniquifier.noderev_txn_id, str));
864
865      str = svn_cstring_tokenize(" ", &substring);
866      if (str == NULL || *str != '_')
867        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
868                                _("Malformed text representation offset line in node-rev"));
869
870      ++str;
871      rep->uniquifier.number = svn__base36toui64(&end, str);
872    }
873
874  if (*end)
875    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
876                            _("Malformed text representation offset line in node-rev"));
877
878  return SVN_NO_ERROR;
879}
880
881/* Wrap svn_fs_fs__parse_representation(), extracting its TXN_ID from our
882   NODEREV_ID, and adding an error message. */
883static svn_error_t *
884read_rep_offsets(representation_t **rep_p,
885                 char *string,
886                 const svn_fs_id_t *noderev_id,
887                 apr_pool_t *result_pool,
888                 apr_pool_t *scratch_pool)
889{
890  svn_error_t *err
891    = svn_fs_fs__parse_representation(rep_p,
892                                      svn_stringbuf_create_wrap(string,
893                                                                scratch_pool),
894                                      result_pool,
895                                      scratch_pool);
896  if (err)
897    {
898      const svn_string_t *id_unparsed;
899      const char *where;
900
901      id_unparsed = svn_fs_fs__id_unparse(noderev_id, scratch_pool);
902      where = apr_psprintf(scratch_pool,
903                           _("While reading representation offsets "
904                             "for node-revision '%s':"),
905                           noderev_id ? id_unparsed->data : "(null)");
906
907      return svn_error_quick_wrap(err, where);
908    }
909
910  if ((*rep_p)->revision == SVN_INVALID_REVNUM)
911    if (noderev_id)
912      (*rep_p)->txn_id = *svn_fs_fs__id_txn_id(noderev_id);
913
914  return SVN_NO_ERROR;
915}
916
917svn_error_t *
918svn_fs_fs__read_noderev(node_revision_t **noderev_p,
919                        svn_stream_t *stream,
920                        apr_pool_t *result_pool,
921                        apr_pool_t *scratch_pool)
922{
923  apr_hash_t *headers;
924  node_revision_t *noderev;
925  char *value;
926  const char *noderev_id;
927
928  SVN_ERR(read_header_block(&headers, stream, scratch_pool));
929
930  noderev = apr_pcalloc(result_pool, sizeof(*noderev));
931
932  /* Read the node-rev id. */
933  value = svn_hash_gets(headers, HEADER_ID);
934  if (value == NULL)
935      /* ### More information: filename/offset coordinates */
936      return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
937                              _("Missing id field in node-rev"));
938
939  SVN_ERR(svn_stream_close(stream));
940
941  SVN_ERR(svn_fs_fs__id_parse(&noderev->id, value, result_pool));
942  noderev_id = value; /* for error messages later */
943
944  /* Read the type. */
945  value = svn_hash_gets(headers, HEADER_TYPE);
946
947  if ((value == NULL) ||
948      (   strcmp(value, SVN_FS_FS__KIND_FILE)
949       && strcmp(value, SVN_FS_FS__KIND_DIR)))
950    /* ### s/kind/type/ */
951    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
952                             _("Missing kind field in node-rev '%s'"),
953                             noderev_id);
954
955  noderev->kind = (strcmp(value, SVN_FS_FS__KIND_FILE) == 0)
956                ? svn_node_file
957                : svn_node_dir;
958
959  /* Read the 'count' field. */
960  value = svn_hash_gets(headers, HEADER_COUNT);
961  if (value)
962    SVN_ERR(svn_cstring_atoi(&noderev->predecessor_count, value));
963  else
964    noderev->predecessor_count = 0;
965
966  /* Get the properties location. */
967  value = svn_hash_gets(headers, HEADER_PROPS);
968  if (value)
969    {
970      SVN_ERR(read_rep_offsets(&noderev->prop_rep, value,
971                               noderev->id, result_pool, scratch_pool));
972    }
973
974  /* Get the data location. */
975  value = svn_hash_gets(headers, HEADER_TEXT);
976  if (value)
977    {
978      SVN_ERR(read_rep_offsets(&noderev->data_rep, value,
979                               noderev->id, result_pool, scratch_pool));
980    }
981
982  /* Get the created path. */
983  value = svn_hash_gets(headers, HEADER_CPATH);
984  if (value == NULL)
985    {
986      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
987                               _("Missing cpath field in node-rev '%s'"),
988                               noderev_id);
989    }
990  else
991    {
992      if (!svn_fspath__is_canonical(value))
993        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
994                            _("Non-canonical cpath field in node-rev '%s'"),
995                            noderev_id);
996
997      noderev->created_path = apr_pstrdup(result_pool, value);
998    }
999
1000  /* Get the predecessor ID. */
1001  value = svn_hash_gets(headers, HEADER_PRED);
1002  if (value)
1003    SVN_ERR(svn_fs_fs__id_parse(&noderev->predecessor_id, value,
1004                                result_pool));
1005
1006  /* Get the copyroot. */
1007  value = svn_hash_gets(headers, HEADER_COPYROOT);
1008  if (value == NULL)
1009    {
1010      noderev->copyroot_path = apr_pstrdup(result_pool, noderev->created_path);
1011      noderev->copyroot_rev = svn_fs_fs__id_rev(noderev->id);
1012    }
1013  else
1014    {
1015      SVN_ERR(parse_revnum(&noderev->copyroot_rev, (const char **)&value));
1016
1017      if (!svn_fspath__is_canonical(value))
1018        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
1019                                 _("Malformed copyroot line in node-rev '%s'"),
1020                                 noderev_id);
1021      noderev->copyroot_path = apr_pstrdup(result_pool, value);
1022    }
1023
1024  /* Get the copyfrom. */
1025  value = svn_hash_gets(headers, HEADER_COPYFROM);
1026  if (value == NULL)
1027    {
1028      noderev->copyfrom_path = NULL;
1029      noderev->copyfrom_rev = SVN_INVALID_REVNUM;
1030    }
1031  else
1032    {
1033      SVN_ERR(parse_revnum(&noderev->copyfrom_rev, (const char **)&value));
1034
1035      if (*value == 0)
1036        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
1037                                 _("Malformed copyfrom line in node-rev '%s'"),
1038                                 noderev_id);
1039      noderev->copyfrom_path = apr_pstrdup(result_pool, value);
1040    }
1041
1042  /* Get whether this is a fresh txn root. */
1043  value = svn_hash_gets(headers, HEADER_FRESHTXNRT);
1044  noderev->is_fresh_txn_root = (value != NULL);
1045
1046  /* Get the mergeinfo count. */
1047  value = svn_hash_gets(headers, HEADER_MINFO_CNT);
1048  if (value)
1049    SVN_ERR(svn_cstring_atoi64(&noderev->mergeinfo_count, value));
1050  else
1051    noderev->mergeinfo_count = 0;
1052
1053  /* Get whether *this* node has mergeinfo. */
1054  value = svn_hash_gets(headers, HEADER_MINFO_HERE);
1055  noderev->has_mergeinfo = (value != NULL);
1056
1057  *noderev_p = noderev;
1058
1059  return SVN_NO_ERROR;
1060}
1061
1062/* Return a textual representation of the DIGEST of given KIND.
1063 * Allocate the result in RESULT_POOL.
1064 */
1065static const char *
1066format_digest(const unsigned char *digest,
1067              svn_checksum_kind_t kind,
1068              apr_pool_t *result_pool)
1069{
1070  svn_checksum_t checksum;
1071  checksum.digest = digest;
1072  checksum.kind = kind;
1073
1074  return svn_checksum_to_cstring_display(&checksum, result_pool);
1075}
1076
1077/* Return a textual representation of the uniquifier represented
1078 * by NODEREV_TXN_ID and NUMBER.  Use POOL for the allocations.
1079 */
1080static const char *
1081format_uniquifier(const svn_fs_fs__id_part_t *noderev_txn_id,
1082                  apr_uint64_t number,
1083                  apr_pool_t *pool)
1084{
1085  char buf[SVN_INT64_BUFFER_SIZE];
1086  const char *txn_id_str;
1087
1088  txn_id_str = svn_fs_fs__id_txn_unparse(noderev_txn_id, pool);
1089  svn__ui64tobase36(buf, number);
1090
1091  return apr_psprintf(pool, "%s/_%s", txn_id_str, buf);
1092}
1093
1094svn_stringbuf_t *
1095svn_fs_fs__unparse_representation(representation_t *rep,
1096                                  int format,
1097                                  svn_boolean_t mutable_rep_truncated,
1098                                  apr_pool_t *result_pool,
1099                                  apr_pool_t *scratch_pool)
1100{
1101  svn_stringbuf_t *str;
1102  const char *sha1_str;
1103  const char *uniquifier_str;
1104
1105  if (svn_fs_fs__id_txn_used(&rep->txn_id) && mutable_rep_truncated)
1106    return svn_stringbuf_ncreate("-1", 2, result_pool);
1107
1108  /* Format of the string:
1109     <rev> <item_index> <size> <expanded-size> <md5> [<sha1>] [<uniquifier>]
1110   */
1111  str = svn_stringbuf_createf(
1112          result_pool,
1113          "%ld"
1114          " %" APR_UINT64_T_FMT
1115          " %" SVN_FILESIZE_T_FMT
1116          " %" SVN_FILESIZE_T_FMT
1117          " %s",
1118          rep->revision,
1119          rep->item_index,
1120          rep->size,
1121          rep->expanded_size,
1122          format_digest(rep->md5_digest, svn_checksum_md5, scratch_pool));
1123
1124  /* Compatibility: these formats don't understand <sha1> and <uniquifier>. */
1125  if (format < SVN_FS_FS__MIN_REP_SHARING_FORMAT)
1126    return str;
1127
1128  if (format < SVN_FS_FS__MIN_REP_STRING_OPTIONAL_VALUES_FORMAT)
1129    {
1130      /* Compatibility: these formats can only have <sha1> and <uniquifier>
1131         present simultaneously, or don't have them at all. */
1132      if (rep->has_sha1)
1133        {
1134          sha1_str = format_digest(rep->sha1_digest, svn_checksum_sha1,
1135                                   scratch_pool);
1136          uniquifier_str = format_uniquifier(&rep->uniquifier.noderev_txn_id,
1137                                             rep->uniquifier.number,
1138                                             scratch_pool);
1139          svn_stringbuf_appendbyte(str, ' ');
1140          svn_stringbuf_appendcstr(str, sha1_str);
1141          svn_stringbuf_appendbyte(str, ' ');
1142          svn_stringbuf_appendcstr(str, uniquifier_str);
1143        }
1144      return str;
1145    }
1146
1147  /* The most recent formats support optional <sha1> and <uniquifier> values. */
1148  if (rep->has_sha1)
1149    {
1150      sha1_str = format_digest(rep->sha1_digest, svn_checksum_sha1,
1151                               scratch_pool);
1152    }
1153  else
1154    sha1_str = "-";
1155
1156  if (rep->uniquifier.number == 0 &&
1157      rep->uniquifier.noderev_txn_id.number == 0 &&
1158      rep->uniquifier.noderev_txn_id.revision == 0)
1159    {
1160      uniquifier_str = "-";
1161    }
1162  else
1163    {
1164      uniquifier_str = format_uniquifier(&rep->uniquifier.noderev_txn_id,
1165                                         rep->uniquifier.number,
1166                                         scratch_pool);
1167    }
1168
1169  svn_stringbuf_appendbyte(str, ' ');
1170  svn_stringbuf_appendcstr(str, sha1_str);
1171  svn_stringbuf_appendbyte(str, ' ');
1172  svn_stringbuf_appendcstr(str, uniquifier_str);
1173
1174  return str;
1175}
1176
1177
1178svn_error_t *
1179svn_fs_fs__write_noderev(svn_stream_t *outfile,
1180                         node_revision_t *noderev,
1181                         int format,
1182                         svn_boolean_t include_mergeinfo,
1183                         apr_pool_t *scratch_pool)
1184{
1185  SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_ID ": %s\n",
1186                            svn_fs_fs__id_unparse(noderev->id,
1187                                                  scratch_pool)->data));
1188
1189  SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_TYPE ": %s\n",
1190                            (noderev->kind == svn_node_file) ?
1191                            SVN_FS_FS__KIND_FILE : SVN_FS_FS__KIND_DIR));
1192
1193  if (noderev->predecessor_id)
1194    SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_PRED ": %s\n",
1195                              svn_fs_fs__id_unparse(noderev->predecessor_id,
1196                                                    scratch_pool)->data));
1197
1198  SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COUNT ": %d\n",
1199                            noderev->predecessor_count));
1200
1201  if (noderev->data_rep)
1202    SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_TEXT ": %s\n",
1203                              svn_fs_fs__unparse_representation
1204                                (noderev->data_rep,
1205                                 format,
1206                                 noderev->kind == svn_node_dir,
1207                                 scratch_pool, scratch_pool)->data));
1208
1209  if (noderev->prop_rep)
1210    SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_PROPS ": %s\n",
1211                              svn_fs_fs__unparse_representation
1212                                (noderev->prop_rep, format,
1213                                 TRUE, scratch_pool, scratch_pool)->data));
1214
1215  SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_CPATH ": %s\n",
1216                            noderev->created_path));
1217
1218  if (noderev->copyfrom_path)
1219    SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COPYFROM ": %ld"
1220                              " %s\n",
1221                              noderev->copyfrom_rev,
1222                              noderev->copyfrom_path));
1223
1224  if ((noderev->copyroot_rev != svn_fs_fs__id_rev(noderev->id)) ||
1225      (strcmp(noderev->copyroot_path, noderev->created_path) != 0))
1226    SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COPYROOT ": %ld"
1227                              " %s\n",
1228                              noderev->copyroot_rev,
1229                              noderev->copyroot_path));
1230
1231  if (noderev->is_fresh_txn_root)
1232    SVN_ERR(svn_stream_puts(outfile, HEADER_FRESHTXNRT ": y\n"));
1233
1234  if (include_mergeinfo)
1235    {
1236      if (noderev->mergeinfo_count > 0)
1237        SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_MINFO_CNT
1238                                  ": %" APR_INT64_T_FMT "\n",
1239                                  noderev->mergeinfo_count));
1240
1241      if (noderev->has_mergeinfo)
1242        SVN_ERR(svn_stream_puts(outfile, HEADER_MINFO_HERE ": y\n"));
1243    }
1244
1245  return svn_stream_puts(outfile, "\n");
1246}
1247
1248svn_error_t *
1249svn_fs_fs__read_rep_header(svn_fs_fs__rep_header_t **header,
1250                           svn_stream_t *stream,
1251                           apr_pool_t *result_pool,
1252                           apr_pool_t *scratch_pool)
1253{
1254  svn_stringbuf_t *buffer;
1255  char *str, *last_str;
1256  apr_int64_t val;
1257  svn_boolean_t eol = FALSE;
1258
1259  SVN_ERR(svn_stream_readline(stream, &buffer, "\n", &eol, scratch_pool));
1260
1261  *header = apr_pcalloc(result_pool, sizeof(**header));
1262  (*header)->header_size = buffer->len + 1;
1263  if (strcmp(buffer->data, REP_PLAIN) == 0)
1264    {
1265      (*header)->type = svn_fs_fs__rep_plain;
1266      return SVN_NO_ERROR;
1267    }
1268
1269  if (strcmp(buffer->data, REP_DELTA) == 0)
1270    {
1271      /* This is a delta against the empty stream. */
1272      (*header)->type = svn_fs_fs__rep_self_delta;
1273      return SVN_NO_ERROR;
1274    }
1275
1276  (*header)->type = svn_fs_fs__rep_delta;
1277
1278  /* We have hopefully a DELTA vs. a non-empty base revision. */
1279  last_str = buffer->data;
1280  str = svn_cstring_tokenize(" ", &last_str);
1281  if (! str || (strcmp(str, REP_DELTA) != 0))
1282    goto error;
1283
1284  SVN_ERR(parse_revnum(&(*header)->base_revision, (const char **)&last_str));
1285
1286  str = svn_cstring_tokenize(" ", &last_str);
1287  if (! str)
1288    goto error;
1289  SVN_ERR(svn_cstring_atoi64(&val, str));
1290  (*header)->base_item_index = (apr_off_t)val;
1291
1292  str = svn_cstring_tokenize(" ", &last_str);
1293  if (! str)
1294    goto error;
1295  SVN_ERR(svn_cstring_atoi64(&val, str));
1296  (*header)->base_length = (svn_filesize_t)val;
1297
1298  return SVN_NO_ERROR;
1299
1300 error:
1301  return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
1302                           _("Malformed representation header"));
1303}
1304
1305svn_error_t *
1306svn_fs_fs__write_rep_header(svn_fs_fs__rep_header_t *header,
1307                            svn_stream_t *stream,
1308                            apr_pool_t *scratch_pool)
1309{
1310  const char *text;
1311
1312  switch (header->type)
1313    {
1314      case svn_fs_fs__rep_plain:
1315        text = REP_PLAIN "\n";
1316        break;
1317
1318      case svn_fs_fs__rep_self_delta:
1319        text = REP_DELTA "\n";
1320        break;
1321
1322      default:
1323        text = apr_psprintf(scratch_pool, REP_DELTA " %ld %" APR_OFF_T_FMT
1324                                          " %" SVN_FILESIZE_T_FMT "\n",
1325                            header->base_revision, header->base_item_index,
1326                            header->base_length);
1327    }
1328
1329  return svn_error_trace(svn_stream_puts(stream, text));
1330}
1331