]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/libsvn_fs_fs/low_level.c
Update svn-1.9.7 to 1.10.0.
[FreeBSD/FreeBSD.git] / contrib / subversion / subversion / libsvn_fs_fs / low_level.c
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  */
78 static svn_error_t *
79 parse_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
103 svn_error_t *
104 svn_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
181 svn_stringbuf_t *
182 svn_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. */
194 static svn_error_t *
195 wrap_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
205 svn_error_t *
206 svn_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                                           "%" APR_UINT64_T_HEX_FMT,
258                                           (apr_uint64_t)*p2l_offset),
259                              apr_psprintf(result_pool,
260                                           "%" 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
276 svn_stringbuf_t *
277 svn_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. */
297 static svn_error_t *
298 read_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
482 svn_error_t *
483 svn_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
517 svn_error_t *
518 svn_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. */
547 static svn_error_t *
548 write_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
627 svn_error_t *
628 svn_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_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. */
684 static svn_error_t *
685 read_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). */
747 svn_error_t *
748 svn_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. */
883 static svn_error_t *
884 read_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
917 svn_error_t *
918 svn_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  */
1065 static const char *
1066 format_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  */
1080 static const char *
1081 format_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
1094 svn_stringbuf_t *
1095 svn_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
1178 svn_error_t *
1179 svn_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
1248 svn_error_t *
1249 svn_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
1305 svn_error_t *
1306 svn_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 }