]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_fs_fs/low_level.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.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 svn_error_t *
193 svn_fs_fs__parse_footer(apr_off_t *l2p_offset,
194                         svn_checksum_t **l2p_checksum,
195                         apr_off_t *p2l_offset,
196                         svn_checksum_t **p2l_checksum,
197                         svn_stringbuf_t *footer,
198                         svn_revnum_t rev,
199                         apr_pool_t *result_pool)
200 {
201   apr_int64_t val;
202   char *last_str = footer->data;
203
204   /* Get the L2P offset. */
205   const char *str = svn_cstring_tokenize(" ", &last_str);
206   if (str == NULL)
207     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
208                             _("Invalid revision footer"));
209
210   SVN_ERR(svn_cstring_atoi64(&val, str));
211   *l2p_offset = (apr_off_t)val;
212
213   /* Get the L2P checksum. */
214   str = svn_cstring_tokenize(" ", &last_str);
215   if (str == NULL)
216     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
217                             _("Invalid revision footer"));
218
219   SVN_ERR(svn_checksum_parse_hex(l2p_checksum, svn_checksum_md5, str,
220                                  result_pool));
221
222   /* Get the P2L offset. */
223   str = svn_cstring_tokenize(" ", &last_str);
224   if (str == NULL)
225     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
226                             _("Invalid revision footer"));
227
228   SVN_ERR(svn_cstring_atoi64(&val, str));
229   *p2l_offset = (apr_off_t)val;
230
231   /* Get the P2L checksum. */
232   str = svn_cstring_tokenize(" ", &last_str);
233   if (str == NULL)
234     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
235                             _("Invalid revision footer"));
236
237   SVN_ERR(svn_checksum_parse_hex(p2l_checksum, svn_checksum_md5, str,
238                                  result_pool));
239
240   return SVN_NO_ERROR;
241 }
242
243 svn_stringbuf_t *
244 svn_fs_fs__unparse_footer(apr_off_t l2p_offset,
245                           svn_checksum_t *l2p_checksum,
246                           apr_off_t p2l_offset,
247                           svn_checksum_t *p2l_checksum,
248                           apr_pool_t *result_pool,
249                           apr_pool_t *scratch_pool)
250 {
251   return svn_stringbuf_createf(result_pool,
252                                "%" APR_OFF_T_FMT " %s %" APR_OFF_T_FMT " %s",
253                                l2p_offset,
254                                svn_checksum_to_cstring(l2p_checksum,
255                                                        scratch_pool),
256                                p2l_offset,
257                                svn_checksum_to_cstring(p2l_checksum,
258                                                        scratch_pool));
259 }
260
261 /* Read the next entry in the changes record from file FILE and store
262    the resulting change in *CHANGE_P.  If there is no next record,
263    store NULL there.  Perform all allocations from POOL. */
264 static svn_error_t *
265 read_change(change_t **change_p,
266             svn_stream_t *stream,
267             apr_pool_t *result_pool,
268             apr_pool_t *scratch_pool)
269 {
270   svn_stringbuf_t *line;
271   svn_boolean_t eof = TRUE;
272   change_t *change;
273   char *str, *last_str, *kind_str;
274   svn_fs_path_change2_t *info;
275
276   /* Default return value. */
277   *change_p = NULL;
278
279   SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, scratch_pool));
280
281   /* Check for a blank line. */
282   if (eof || (line->len == 0))
283     return SVN_NO_ERROR;
284
285   change = apr_pcalloc(result_pool, sizeof(*change));
286   info = &change->info;
287   last_str = line->data;
288
289   /* Get the node-id of the change. */
290   str = svn_cstring_tokenize(" ", &last_str);
291   if (str == NULL)
292     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
293                             _("Invalid changes line in rev-file"));
294
295   SVN_ERR(svn_fs_fs__id_parse(&info->node_rev_id, str, result_pool));
296   if (info->node_rev_id == NULL)
297     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
298                             _("Invalid changes line in rev-file"));
299
300   /* Get the change type. */
301   str = svn_cstring_tokenize(" ", &last_str);
302   if (str == NULL)
303     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
304                             _("Invalid changes line in rev-file"));
305
306   /* Don't bother to check the format number before looking for
307    * node-kinds: just read them if you find them. */
308   info->node_kind = svn_node_unknown;
309   kind_str = strchr(str, '-');
310   if (kind_str)
311     {
312       /* Cap off the end of "str" (the action). */
313       *kind_str = '\0';
314       kind_str++;
315       if (strcmp(kind_str, SVN_FS_FS__KIND_FILE) == 0)
316         info->node_kind = svn_node_file;
317       else if (strcmp(kind_str, SVN_FS_FS__KIND_DIR) == 0)
318         info->node_kind = svn_node_dir;
319       else
320         return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
321                                 _("Invalid changes line in rev-file"));
322     }
323
324   if (strcmp(str, ACTION_MODIFY) == 0)
325     {
326       info->change_kind = svn_fs_path_change_modify;
327     }
328   else if (strcmp(str, ACTION_ADD) == 0)
329     {
330       info->change_kind = svn_fs_path_change_add;
331     }
332   else if (strcmp(str, ACTION_DELETE) == 0)
333     {
334       info->change_kind = svn_fs_path_change_delete;
335     }
336   else if (strcmp(str, ACTION_REPLACE) == 0)
337     {
338       info->change_kind = svn_fs_path_change_replace;
339     }
340   else if (strcmp(str, ACTION_RESET) == 0)
341     {
342       info->change_kind = svn_fs_path_change_reset;
343     }
344   else
345     {
346       return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
347                               _("Invalid change kind in rev file"));
348     }
349
350   /* Get the text-mod flag. */
351   str = svn_cstring_tokenize(" ", &last_str);
352   if (str == NULL)
353     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
354                             _("Invalid changes line in rev-file"));
355
356   if (strcmp(str, FLAG_TRUE) == 0)
357     {
358       info->text_mod = TRUE;
359     }
360   else if (strcmp(str, FLAG_FALSE) == 0)
361     {
362       info->text_mod = FALSE;
363     }
364   else
365     {
366       return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
367                               _("Invalid text-mod flag in rev-file"));
368     }
369
370   /* Get the prop-mod flag. */
371   str = svn_cstring_tokenize(" ", &last_str);
372   if (str == NULL)
373     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
374                             _("Invalid changes line in rev-file"));
375
376   if (strcmp(str, FLAG_TRUE) == 0)
377     {
378       info->prop_mod = TRUE;
379     }
380   else if (strcmp(str, FLAG_FALSE) == 0)
381     {
382       info->prop_mod = FALSE;
383     }
384   else
385     {
386       return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
387                               _("Invalid prop-mod flag in rev-file"));
388     }
389
390   /* Get the mergeinfo-mod flag if given.  Otherwise, the next thing
391      is the path starting with a slash.  Also, we must initialize the
392      flag explicitly because 0 is not valid for a svn_tristate_t. */
393   info->mergeinfo_mod = svn_tristate_unknown;
394   if (*last_str != '/')
395     {
396       str = svn_cstring_tokenize(" ", &last_str);
397       if (str == NULL)
398         return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
399                                 _("Invalid changes line in rev-file"));
400
401       if (strcmp(str, FLAG_TRUE) == 0)
402         {
403           info->mergeinfo_mod = svn_tristate_true;
404         }
405       else if (strcmp(str, FLAG_FALSE) == 0)
406         {
407           info->mergeinfo_mod = svn_tristate_false;
408         }
409       else
410         {
411           return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
412                               _("Invalid mergeinfo-mod flag in rev-file"));
413         }
414     }
415
416   /* Get the changed path. */
417   if (!svn_fspath__is_canonical(last_str))
418     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
419                             _("Invalid path in changes line"));
420
421   change->path.len = strlen(last_str);
422   change->path.data = apr_pstrdup(result_pool, last_str);
423
424   /* Read the next line, the copyfrom line. */
425   SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, scratch_pool));
426   info->copyfrom_known = TRUE;
427   if (eof || line->len == 0)
428     {
429       info->copyfrom_rev = SVN_INVALID_REVNUM;
430       info->copyfrom_path = NULL;
431     }
432   else
433     {
434       last_str = line->data;
435       SVN_ERR(parse_revnum(&info->copyfrom_rev, (const char **)&last_str));
436
437       if (!svn_fspath__is_canonical(last_str))
438         return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
439                                 _("Invalid copy-from path in changes line"));
440
441       info->copyfrom_path = apr_pstrdup(result_pool, last_str);
442     }
443
444   *change_p = change;
445
446   return SVN_NO_ERROR;
447 }
448
449 svn_error_t *
450 svn_fs_fs__read_changes(apr_array_header_t **changes,
451                         svn_stream_t *stream,
452                         apr_pool_t *result_pool,
453                         apr_pool_t *scratch_pool)
454 {
455   change_t *change;
456   apr_pool_t *iterpool;
457
458   /* Pre-allocate enough room for most change lists.
459      (will be auto-expanded as necessary).
460
461      Chose the default to just below 2^N such that the doubling reallocs
462      will request roughly 2^M bytes from the OS without exceeding the
463      respective two-power by just a few bytes (leaves room array and APR
464      node overhead for large enough M).
465    */
466   *changes = apr_array_make(result_pool, 63, sizeof(change_t *));
467
468   SVN_ERR(read_change(&change, stream, result_pool, scratch_pool));
469   iterpool = svn_pool_create(scratch_pool);
470   while (change)
471     {
472       APR_ARRAY_PUSH(*changes, change_t*) = change;
473       SVN_ERR(read_change(&change, stream, result_pool, iterpool));
474       svn_pool_clear(iterpool);
475     }
476   svn_pool_destroy(iterpool);
477
478   return SVN_NO_ERROR;
479 }
480
481 svn_error_t *
482 svn_fs_fs__read_changes_incrementally(svn_stream_t *stream,
483                                       svn_fs_fs__change_receiver_t
484                                         change_receiver,
485                                       void *change_receiver_baton,
486                                       apr_pool_t *scratch_pool)
487 {
488   change_t *change;
489   apr_pool_t *iterpool;
490
491   iterpool = svn_pool_create(scratch_pool);
492   do
493     {
494       svn_pool_clear(iterpool);
495
496       SVN_ERR(read_change(&change, stream, iterpool, iterpool));
497       if (change)
498         SVN_ERR(change_receiver(change_receiver_baton, change, iterpool));
499     }
500   while (change);
501   svn_pool_destroy(iterpool);
502
503   return SVN_NO_ERROR;
504 }
505
506 /* Write a single change entry, path PATH, change CHANGE, to STREAM.
507
508    Only include the node kind field if INCLUDE_NODE_KIND is true.  Only
509    include the mergeinfo-mod field if INCLUDE_MERGEINFO_MODS is true.
510    All temporary allocations are in SCRATCH_POOL. */
511 static svn_error_t *
512 write_change_entry(svn_stream_t *stream,
513                    const char *path,
514                    svn_fs_path_change2_t *change,
515                    svn_boolean_t include_node_kind,
516                    svn_boolean_t include_mergeinfo_mods,
517                    apr_pool_t *scratch_pool)
518 {
519   const char *idstr;
520   const char *change_string = NULL;
521   const char *kind_string = "";
522   const char *mergeinfo_string = "";
523   svn_stringbuf_t *buf;
524   apr_size_t len;
525
526   switch (change->change_kind)
527     {
528     case svn_fs_path_change_modify:
529       change_string = ACTION_MODIFY;
530       break;
531     case svn_fs_path_change_add:
532       change_string = ACTION_ADD;
533       break;
534     case svn_fs_path_change_delete:
535       change_string = ACTION_DELETE;
536       break;
537     case svn_fs_path_change_replace:
538       change_string = ACTION_REPLACE;
539       break;
540     case svn_fs_path_change_reset:
541       change_string = ACTION_RESET;
542       break;
543     default:
544       return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
545                                _("Invalid change type %d"),
546                                change->change_kind);
547     }
548
549   if (change->node_rev_id)
550     idstr = svn_fs_fs__id_unparse(change->node_rev_id, scratch_pool)->data;
551   else
552     idstr = ACTION_RESET;
553
554   if (include_node_kind)
555     {
556       SVN_ERR_ASSERT(change->node_kind == svn_node_dir
557                      || change->node_kind == svn_node_file);
558       kind_string = apr_psprintf(scratch_pool, "-%s",
559                                  change->node_kind == svn_node_dir
560                                  ? SVN_FS_FS__KIND_DIR
561                                   : SVN_FS_FS__KIND_FILE);
562     }
563
564   if (include_mergeinfo_mods && change->mergeinfo_mod != svn_tristate_unknown)
565     mergeinfo_string = apr_psprintf(scratch_pool, " %s",
566                                     change->mergeinfo_mod == svn_tristate_true
567                                       ? FLAG_TRUE
568                                       : FLAG_FALSE);
569
570   buf = svn_stringbuf_createf(scratch_pool, "%s %s%s %s %s%s %s\n",
571                               idstr, change_string, kind_string,
572                               change->text_mod ? FLAG_TRUE : FLAG_FALSE,
573                               change->prop_mod ? FLAG_TRUE : FLAG_FALSE,
574                               mergeinfo_string,
575                               path);
576
577   if (SVN_IS_VALID_REVNUM(change->copyfrom_rev))
578     {
579       svn_stringbuf_appendcstr(buf, apr_psprintf(scratch_pool, "%ld %s",
580                                                  change->copyfrom_rev,
581                                                  change->copyfrom_path));
582     }
583
584    svn_stringbuf_appendbyte(buf, '\n');
585
586    /* Write all change info in one write call. */
587    len = buf->len;
588    return svn_error_trace(svn_stream_write(stream, buf->data, &len));
589 }
590
591 svn_error_t *
592 svn_fs_fs__write_changes(svn_stream_t *stream,
593                          svn_fs_t *fs,
594                          apr_hash_t *changes,
595                          svn_boolean_t terminate_list,
596                          apr_pool_t *scratch_pool)
597 {
598   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
599   fs_fs_data_t *ffd = fs->fsap_data;
600   svn_boolean_t include_node_kinds =
601       ffd->format >= SVN_FS_FS__MIN_KIND_IN_CHANGED_FORMAT;
602   svn_boolean_t include_mergeinfo_mods =
603       ffd->format >= SVN_FS_FS__MIN_MERGEINFO_IN_CHANGED_FORMAT;
604   apr_array_header_t *sorted_changed_paths;
605   int i;
606
607   /* For the sake of the repository administrator sort the changes so
608      that the final file is deterministic and repeatable, however the
609      rest of the FSFS code doesn't require any particular order here.
610
611      Also, this sorting is only effective in writing all entries with
612      a single call as write_final_changed_path_info() does.  For the
613      list being written incrementally during transaction, we actually
614      *must not* change the order of entries from different calls.
615    */
616   sorted_changed_paths = svn_sort__hash(changes,
617                                         svn_sort_compare_items_lexically,
618                                         scratch_pool);
619
620   /* Write all items to disk in the new order. */
621   for (i = 0; i < sorted_changed_paths->nelts; ++i)
622     {
623       svn_fs_path_change2_t *change;
624       const char *path;
625
626       svn_pool_clear(iterpool);
627
628       change = APR_ARRAY_IDX(sorted_changed_paths, i, svn_sort__item_t).value;
629       path = APR_ARRAY_IDX(sorted_changed_paths, i, svn_sort__item_t).key;
630
631       /* Write out the new entry into the final rev-file. */
632       SVN_ERR(write_change_entry(stream, path, change, include_node_kinds,
633                                  include_mergeinfo_mods, iterpool));
634     }
635
636   if (terminate_list)
637     svn_stream_puts(stream, "\n");
638
639   svn_pool_destroy(iterpool);
640
641   return SVN_NO_ERROR;
642 }
643
644 /* Given a revision file FILE that has been pre-positioned at the
645    beginning of a Node-Rev header block, read in that header block and
646    store it in the apr_hash_t HEADERS.  All allocations will be from
647    RESULT_POOL. */
648 static svn_error_t *
649 read_header_block(apr_hash_t **headers,
650                   svn_stream_t *stream,
651                   apr_pool_t *result_pool)
652 {
653   *headers = svn_hash__make(result_pool);
654
655   while (1)
656     {
657       svn_stringbuf_t *header_str;
658       const char *name, *value;
659       apr_size_t i = 0;
660       apr_size_t name_len;
661       svn_boolean_t eof;
662
663       SVN_ERR(svn_stream_readline(stream, &header_str, "\n", &eof,
664                                   result_pool));
665
666       if (eof || header_str->len == 0)
667         break; /* end of header block */
668
669       while (header_str->data[i] != ':')
670         {
671           if (header_str->data[i] == '\0')
672             return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
673                                      _("Found malformed header '%s' in "
674                                        "revision file"),
675                                      header_str->data);
676           i++;
677         }
678
679       /* Create a 'name' string and point to it. */
680       header_str->data[i] = '\0';
681       name = header_str->data;
682       name_len = i;
683
684       /* Check if we have enough data to parse. */
685       if (i + 2 > header_str->len)
686         {
687           /* Restore the original line for the error. */
688           header_str->data[i] = ':';
689           return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
690                                    _("Found malformed header '%s' in "
691                                      "revision file"),
692                                    header_str->data);
693         }
694
695       /* Skip over the NULL byte and the space following it. */
696       i += 2;
697
698       value = header_str->data + i;
699
700       /* header_str is safely in our pool, so we can use bits of it as
701          key and value. */
702       apr_hash_set(*headers, name, name_len, value);
703     }
704
705   return SVN_NO_ERROR;
706 }
707
708 svn_error_t *
709 svn_fs_fs__parse_representation(representation_t **rep_p,
710                                 svn_stringbuf_t *text,
711                                 apr_pool_t *result_pool,
712                                 apr_pool_t *scratch_pool)
713 {
714   representation_t *rep;
715   char *str;
716   apr_int64_t val;
717   char *string = text->data;
718   svn_checksum_t *checksum;
719   const char *end;
720
721   rep = apr_pcalloc(result_pool, sizeof(*rep));
722   *rep_p = rep;
723
724   SVN_ERR(parse_revnum(&rep->revision, (const char **)&string));
725
726   /* initialize transaction info (never stored) */
727   svn_fs_fs__id_txn_reset(&rep->txn_id);
728
729   /* while in transactions, it is legal to simply write "-1" */
730   str = svn_cstring_tokenize(" ", &string);
731   if (str == NULL)
732     {
733       if (rep->revision == SVN_INVALID_REVNUM)
734         return SVN_NO_ERROR;
735
736       return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
737                               _("Malformed text representation offset line in node-rev"));
738     }
739
740   SVN_ERR(svn_cstring_atoi64(&val, str));
741   rep->item_index = (apr_uint64_t)val;
742
743   str = svn_cstring_tokenize(" ", &string);
744   if (str == NULL)
745     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
746                             _("Malformed text representation offset line in node-rev"));
747
748   SVN_ERR(svn_cstring_atoi64(&val, str));
749   rep->size = (svn_filesize_t)val;
750
751   str = svn_cstring_tokenize(" ", &string);
752   if (str == NULL)
753     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
754                             _("Malformed text representation offset line in node-rev"));
755
756   SVN_ERR(svn_cstring_atoi64(&val, str));
757   rep->expanded_size = (svn_filesize_t)val;
758
759   /* Read in the MD5 hash. */
760   str = svn_cstring_tokenize(" ", &string);
761   if ((str == NULL) || (strlen(str) != (APR_MD5_DIGESTSIZE * 2)))
762     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
763                             _("Malformed text representation offset line in node-rev"));
764
765   SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_md5, str,
766                                  scratch_pool));
767   memcpy(rep->md5_digest, checksum->digest, sizeof(rep->md5_digest));
768
769   /* The remaining fields are only used for formats >= 4, so check that. */
770   str = svn_cstring_tokenize(" ", &string);
771   if (str == NULL)
772     return SVN_NO_ERROR;
773
774   /* Read the SHA1 hash. */
775   if (strlen(str) != (APR_SHA1_DIGESTSIZE * 2))
776     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
777                             _("Malformed text representation offset line in node-rev"));
778
779   SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_sha1, str,
780                                  scratch_pool));
781   rep->has_sha1 = checksum != NULL;
782   memcpy(rep->sha1_digest, checksum->digest, sizeof(rep->sha1_digest));
783
784   /* Read the uniquifier. */
785   str = svn_cstring_tokenize("/", &string);
786   if (str == NULL)
787     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
788                             _("Malformed text representation offset line in node-rev"));
789
790   SVN_ERR(svn_fs_fs__id_txn_parse(&rep->uniquifier.noderev_txn_id, str));
791
792   str = svn_cstring_tokenize(" ", &string);
793   if (str == NULL || *str != '_')
794     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
795                             _("Malformed text representation offset line in node-rev"));
796
797   ++str;
798   rep->uniquifier.number = svn__base36toui64(&end, str);
799
800   if (*end)
801     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
802                             _("Malformed text representation offset line in node-rev"));
803
804   return SVN_NO_ERROR;
805 }
806
807 /* Wrap svn_fs_fs__parse_representation(), extracting its TXN_ID from our
808    NODEREV_ID, and adding an error message. */
809 static svn_error_t *
810 read_rep_offsets(representation_t **rep_p,
811                  char *string,
812                  const svn_fs_id_t *noderev_id,
813                  apr_pool_t *result_pool,
814                  apr_pool_t *scratch_pool)
815 {
816   svn_error_t *err
817     = svn_fs_fs__parse_representation(rep_p,
818                                       svn_stringbuf_create_wrap(string,
819                                                                 scratch_pool),
820                                       result_pool,
821                                       scratch_pool);
822   if (err)
823     {
824       const svn_string_t *id_unparsed;
825       const char *where;
826
827       id_unparsed = svn_fs_fs__id_unparse(noderev_id, scratch_pool);
828       where = apr_psprintf(scratch_pool,
829                            _("While reading representation offsets "
830                              "for node-revision '%s':"),
831                            noderev_id ? id_unparsed->data : "(null)");
832
833       return svn_error_quick_wrap(err, where);
834     }
835
836   if ((*rep_p)->revision == SVN_INVALID_REVNUM)
837     if (noderev_id)
838       (*rep_p)->txn_id = *svn_fs_fs__id_txn_id(noderev_id);
839
840   return SVN_NO_ERROR;
841 }
842
843 svn_error_t *
844 svn_fs_fs__read_noderev(node_revision_t **noderev_p,
845                         svn_stream_t *stream,
846                         apr_pool_t *result_pool,
847                         apr_pool_t *scratch_pool)
848 {
849   apr_hash_t *headers;
850   node_revision_t *noderev;
851   char *value;
852   const char *noderev_id;
853
854   SVN_ERR(read_header_block(&headers, stream, scratch_pool));
855
856   noderev = apr_pcalloc(result_pool, sizeof(*noderev));
857
858   /* Read the node-rev id. */
859   value = svn_hash_gets(headers, HEADER_ID);
860   if (value == NULL)
861       /* ### More information: filename/offset coordinates */
862       return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
863                               _("Missing id field in node-rev"));
864
865   SVN_ERR(svn_stream_close(stream));
866
867   SVN_ERR(svn_fs_fs__id_parse(&noderev->id, value, result_pool));
868   noderev_id = value; /* for error messages later */
869
870   /* Read the type. */
871   value = svn_hash_gets(headers, HEADER_TYPE);
872
873   if ((value == NULL) ||
874       (   strcmp(value, SVN_FS_FS__KIND_FILE)
875        && strcmp(value, SVN_FS_FS__KIND_DIR)))
876     /* ### s/kind/type/ */
877     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
878                              _("Missing kind field in node-rev '%s'"),
879                              noderev_id);
880
881   noderev->kind = (strcmp(value, SVN_FS_FS__KIND_FILE) == 0)
882                 ? svn_node_file
883                 : svn_node_dir;
884
885   /* Read the 'count' field. */
886   value = svn_hash_gets(headers, HEADER_COUNT);
887   if (value)
888     SVN_ERR(svn_cstring_atoi(&noderev->predecessor_count, value));
889   else
890     noderev->predecessor_count = 0;
891
892   /* Get the properties location. */
893   value = svn_hash_gets(headers, HEADER_PROPS);
894   if (value)
895     {
896       SVN_ERR(read_rep_offsets(&noderev->prop_rep, value,
897                                noderev->id, result_pool, scratch_pool));
898     }
899
900   /* Get the data location. */
901   value = svn_hash_gets(headers, HEADER_TEXT);
902   if (value)
903     {
904       SVN_ERR(read_rep_offsets(&noderev->data_rep, value,
905                                noderev->id, result_pool, scratch_pool));
906     }
907
908   /* Get the created path. */
909   value = svn_hash_gets(headers, HEADER_CPATH);
910   if (value == NULL)
911     {
912       return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
913                                _("Missing cpath field in node-rev '%s'"),
914                                noderev_id);
915     }
916   else
917     {
918       if (!svn_fspath__is_canonical(value))
919         return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
920                             _("Non-canonical cpath field in node-rev '%s'"),
921                             noderev_id);
922
923       noderev->created_path = apr_pstrdup(result_pool, value);
924     }
925
926   /* Get the predecessor ID. */
927   value = svn_hash_gets(headers, HEADER_PRED);
928   if (value)
929     SVN_ERR(svn_fs_fs__id_parse(&noderev->predecessor_id, value,
930                                 result_pool));
931
932   /* Get the copyroot. */
933   value = svn_hash_gets(headers, HEADER_COPYROOT);
934   if (value == NULL)
935     {
936       noderev->copyroot_path = apr_pstrdup(result_pool, noderev->created_path);
937       noderev->copyroot_rev = svn_fs_fs__id_rev(noderev->id);
938     }
939   else
940     {
941       SVN_ERR(parse_revnum(&noderev->copyroot_rev, (const char **)&value));
942
943       if (!svn_fspath__is_canonical(value))
944         return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
945                                  _("Malformed copyroot line in node-rev '%s'"),
946                                  noderev_id);
947       noderev->copyroot_path = apr_pstrdup(result_pool, value);
948     }
949
950   /* Get the copyfrom. */
951   value = svn_hash_gets(headers, HEADER_COPYFROM);
952   if (value == NULL)
953     {
954       noderev->copyfrom_path = NULL;
955       noderev->copyfrom_rev = SVN_INVALID_REVNUM;
956     }
957   else
958     {
959       SVN_ERR(parse_revnum(&noderev->copyfrom_rev, (const char **)&value));
960
961       if (*value == 0)
962         return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
963                                  _("Malformed copyfrom line in node-rev '%s'"),
964                                  noderev_id);
965       noderev->copyfrom_path = apr_pstrdup(result_pool, value);
966     }
967
968   /* Get whether this is a fresh txn root. */
969   value = svn_hash_gets(headers, HEADER_FRESHTXNRT);
970   noderev->is_fresh_txn_root = (value != NULL);
971
972   /* Get the mergeinfo count. */
973   value = svn_hash_gets(headers, HEADER_MINFO_CNT);
974   if (value)
975     SVN_ERR(svn_cstring_atoi64(&noderev->mergeinfo_count, value));
976   else
977     noderev->mergeinfo_count = 0;
978
979   /* Get whether *this* node has mergeinfo. */
980   value = svn_hash_gets(headers, HEADER_MINFO_HERE);
981   noderev->has_mergeinfo = (value != NULL);
982
983   *noderev_p = noderev;
984
985   return SVN_NO_ERROR;
986 }
987
988 /* Return a textual representation of the DIGEST of given KIND.
989  * If IS_NULL is TRUE, no digest is available.
990  * Allocate the result in RESULT_POOL.
991  */
992 static const char *
993 format_digest(const unsigned char *digest,
994               svn_checksum_kind_t kind,
995               svn_boolean_t is_null,
996               apr_pool_t *result_pool)
997 {
998   svn_checksum_t checksum;
999   checksum.digest = digest;
1000   checksum.kind = kind;
1001
1002   if (is_null)
1003     return "(null)";
1004
1005   return svn_checksum_to_cstring_display(&checksum, result_pool);
1006 }
1007
1008 svn_stringbuf_t *
1009 svn_fs_fs__unparse_representation(representation_t *rep,
1010                                   int format,
1011                                   svn_boolean_t mutable_rep_truncated,
1012                                   apr_pool_t *result_pool,
1013                                   apr_pool_t *scratch_pool)
1014 {
1015   char buffer[SVN_INT64_BUFFER_SIZE];
1016   if (svn_fs_fs__id_txn_used(&rep->txn_id) && mutable_rep_truncated)
1017     return svn_stringbuf_ncreate("-1", 2, result_pool);
1018
1019   if (format < SVN_FS_FS__MIN_REP_SHARING_FORMAT || !rep->has_sha1)
1020     return svn_stringbuf_createf
1021             (result_pool, "%ld %" APR_UINT64_T_FMT " %" SVN_FILESIZE_T_FMT
1022              " %" SVN_FILESIZE_T_FMT " %s",
1023              rep->revision, rep->item_index, rep->size,
1024              rep->expanded_size,
1025              format_digest(rep->md5_digest, svn_checksum_md5, FALSE,
1026                            scratch_pool));
1027
1028   svn__ui64tobase36(buffer, rep->uniquifier.number);
1029   return svn_stringbuf_createf
1030           (result_pool, "%ld %" APR_UINT64_T_FMT " %" SVN_FILESIZE_T_FMT
1031            " %" SVN_FILESIZE_T_FMT " %s %s %s/_%s",
1032            rep->revision, rep->item_index, rep->size,
1033            rep->expanded_size,
1034            format_digest(rep->md5_digest, svn_checksum_md5,
1035                          FALSE, scratch_pool),
1036            format_digest(rep->sha1_digest, svn_checksum_sha1,
1037                          !rep->has_sha1, scratch_pool),
1038            svn_fs_fs__id_txn_unparse(&rep->uniquifier.noderev_txn_id,
1039                                      scratch_pool),
1040            buffer);
1041 }
1042
1043
1044 svn_error_t *
1045 svn_fs_fs__write_noderev(svn_stream_t *outfile,
1046                          node_revision_t *noderev,
1047                          int format,
1048                          svn_boolean_t include_mergeinfo,
1049                          apr_pool_t *scratch_pool)
1050 {
1051   SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_ID ": %s\n",
1052                             svn_fs_fs__id_unparse(noderev->id,
1053                                                   scratch_pool)->data));
1054
1055   SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_TYPE ": %s\n",
1056                             (noderev->kind == svn_node_file) ?
1057                             SVN_FS_FS__KIND_FILE : SVN_FS_FS__KIND_DIR));
1058
1059   if (noderev->predecessor_id)
1060     SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_PRED ": %s\n",
1061                               svn_fs_fs__id_unparse(noderev->predecessor_id,
1062                                                     scratch_pool)->data));
1063
1064   SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COUNT ": %d\n",
1065                             noderev->predecessor_count));
1066
1067   if (noderev->data_rep)
1068     SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_TEXT ": %s\n",
1069                               svn_fs_fs__unparse_representation
1070                                 (noderev->data_rep,
1071                                  format,
1072                                  noderev->kind == svn_node_dir,
1073                                  scratch_pool, scratch_pool)->data));
1074
1075   if (noderev->prop_rep)
1076     SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_PROPS ": %s\n",
1077                               svn_fs_fs__unparse_representation
1078                                 (noderev->prop_rep, format,
1079                                  TRUE, scratch_pool, scratch_pool)->data));
1080
1081   SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_CPATH ": %s\n",
1082                             noderev->created_path));
1083
1084   if (noderev->copyfrom_path)
1085     SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COPYFROM ": %ld"
1086                               " %s\n",
1087                               noderev->copyfrom_rev,
1088                               noderev->copyfrom_path));
1089
1090   if ((noderev->copyroot_rev != svn_fs_fs__id_rev(noderev->id)) ||
1091       (strcmp(noderev->copyroot_path, noderev->created_path) != 0))
1092     SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COPYROOT ": %ld"
1093                               " %s\n",
1094                               noderev->copyroot_rev,
1095                               noderev->copyroot_path));
1096
1097   if (noderev->is_fresh_txn_root)
1098     SVN_ERR(svn_stream_puts(outfile, HEADER_FRESHTXNRT ": y\n"));
1099
1100   if (include_mergeinfo)
1101     {
1102       if (noderev->mergeinfo_count > 0)
1103         SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_MINFO_CNT
1104                                   ": %" APR_INT64_T_FMT "\n",
1105                                   noderev->mergeinfo_count));
1106
1107       if (noderev->has_mergeinfo)
1108         SVN_ERR(svn_stream_puts(outfile, HEADER_MINFO_HERE ": y\n"));
1109     }
1110
1111   return svn_stream_puts(outfile, "\n");
1112 }
1113
1114 svn_error_t *
1115 svn_fs_fs__read_rep_header(svn_fs_fs__rep_header_t **header,
1116                            svn_stream_t *stream,
1117                            apr_pool_t *result_pool,
1118                            apr_pool_t *scratch_pool)
1119 {
1120   svn_stringbuf_t *buffer;
1121   char *str, *last_str;
1122   apr_int64_t val;
1123   svn_boolean_t eol = FALSE;
1124
1125   SVN_ERR(svn_stream_readline(stream, &buffer, "\n", &eol, scratch_pool));
1126
1127   *header = apr_pcalloc(result_pool, sizeof(**header));
1128   (*header)->header_size = buffer->len + 1;
1129   if (strcmp(buffer->data, REP_PLAIN) == 0)
1130     {
1131       (*header)->type = svn_fs_fs__rep_plain;
1132       return SVN_NO_ERROR;
1133     }
1134
1135   if (strcmp(buffer->data, REP_DELTA) == 0)
1136     {
1137       /* This is a delta against the empty stream. */
1138       (*header)->type = svn_fs_fs__rep_self_delta;
1139       return SVN_NO_ERROR;
1140     }
1141
1142   (*header)->type = svn_fs_fs__rep_delta;
1143
1144   /* We have hopefully a DELTA vs. a non-empty base revision. */
1145   last_str = buffer->data;
1146   str = svn_cstring_tokenize(" ", &last_str);
1147   if (! str || (strcmp(str, REP_DELTA) != 0))
1148     goto error;
1149
1150   SVN_ERR(parse_revnum(&(*header)->base_revision, (const char **)&last_str));
1151
1152   str = svn_cstring_tokenize(" ", &last_str);
1153   if (! str)
1154     goto error;
1155   SVN_ERR(svn_cstring_atoi64(&val, str));
1156   (*header)->base_item_index = (apr_off_t)val;
1157
1158   str = svn_cstring_tokenize(" ", &last_str);
1159   if (! str)
1160     goto error;
1161   SVN_ERR(svn_cstring_atoi64(&val, str));
1162   (*header)->base_length = (svn_filesize_t)val;
1163
1164   return SVN_NO_ERROR;
1165
1166  error:
1167   return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
1168                            _("Malformed representation header"));
1169 }
1170
1171 svn_error_t *
1172 svn_fs_fs__write_rep_header(svn_fs_fs__rep_header_t *header,
1173                             svn_stream_t *stream,
1174                             apr_pool_t *scratch_pool)
1175 {
1176   const char *text;
1177
1178   switch (header->type)
1179     {
1180       case svn_fs_fs__rep_plain:
1181         text = REP_PLAIN "\n";
1182         break;
1183
1184       case svn_fs_fs__rep_self_delta:
1185         text = REP_DELTA "\n";
1186         break;
1187
1188       default:
1189         text = apr_psprintf(scratch_pool, REP_DELTA " %ld %" APR_OFF_T_FMT
1190                                           " %" SVN_FILESIZE_T_FMT "\n",
1191                             header->base_revision, header->base_item_index,
1192                             header->base_length);
1193     }
1194
1195   return svn_error_trace(svn_stream_puts(stream, text));
1196 }