]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_fs_fs/low_level.c
MFC r309356: svn 1.9.4 -> 1.9.5
[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
768   /* If STR is a all-zero checksum, CHECKSUM will be NULL and REP already
769      contains the correct value. */
770   if (checksum)
771     memcpy(rep->md5_digest, checksum->digest, sizeof(rep->md5_digest));
772
773   /* The remaining fields are only used for formats >= 4, so check that. */
774   str = svn_cstring_tokenize(" ", &string);
775   if (str == NULL)
776     return SVN_NO_ERROR;
777
778   /* Read the SHA1 hash. */
779   if (strlen(str) != (APR_SHA1_DIGESTSIZE * 2))
780     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
781                             _("Malformed text representation offset line in node-rev"));
782
783   SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_sha1, str,
784                                  scratch_pool));
785
786   /* We do have a valid SHA1 but it might be all 0.
787      We cannot be sure where that came from (Alas! legacy), so let's not
788      claim we know the SHA1 in that case. */
789   rep->has_sha1 = checksum != NULL;
790
791   /* If STR is a all-zero checksum, CHECKSUM will be NULL and REP already
792      contains the correct value. */
793   if (checksum)
794     memcpy(rep->sha1_digest, checksum->digest, sizeof(rep->sha1_digest));
795
796   /* Read the uniquifier. */
797   str = svn_cstring_tokenize("/", &string);
798   if (str == NULL)
799     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
800                             _("Malformed text representation offset line in node-rev"));
801
802   SVN_ERR(svn_fs_fs__id_txn_parse(&rep->uniquifier.noderev_txn_id, str));
803
804   str = svn_cstring_tokenize(" ", &string);
805   if (str == NULL || *str != '_')
806     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
807                             _("Malformed text representation offset line in node-rev"));
808
809   ++str;
810   rep->uniquifier.number = svn__base36toui64(&end, str);
811
812   if (*end)
813     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
814                             _("Malformed text representation offset line in node-rev"));
815
816   return SVN_NO_ERROR;
817 }
818
819 /* Wrap svn_fs_fs__parse_representation(), extracting its TXN_ID from our
820    NODEREV_ID, and adding an error message. */
821 static svn_error_t *
822 read_rep_offsets(representation_t **rep_p,
823                  char *string,
824                  const svn_fs_id_t *noderev_id,
825                  apr_pool_t *result_pool,
826                  apr_pool_t *scratch_pool)
827 {
828   svn_error_t *err
829     = svn_fs_fs__parse_representation(rep_p,
830                                       svn_stringbuf_create_wrap(string,
831                                                                 scratch_pool),
832                                       result_pool,
833                                       scratch_pool);
834   if (err)
835     {
836       const svn_string_t *id_unparsed;
837       const char *where;
838
839       id_unparsed = svn_fs_fs__id_unparse(noderev_id, scratch_pool);
840       where = apr_psprintf(scratch_pool,
841                            _("While reading representation offsets "
842                              "for node-revision '%s':"),
843                            noderev_id ? id_unparsed->data : "(null)");
844
845       return svn_error_quick_wrap(err, where);
846     }
847
848   if ((*rep_p)->revision == SVN_INVALID_REVNUM)
849     if (noderev_id)
850       (*rep_p)->txn_id = *svn_fs_fs__id_txn_id(noderev_id);
851
852   return SVN_NO_ERROR;
853 }
854
855 svn_error_t *
856 svn_fs_fs__read_noderev(node_revision_t **noderev_p,
857                         svn_stream_t *stream,
858                         apr_pool_t *result_pool,
859                         apr_pool_t *scratch_pool)
860 {
861   apr_hash_t *headers;
862   node_revision_t *noderev;
863   char *value;
864   const char *noderev_id;
865
866   SVN_ERR(read_header_block(&headers, stream, scratch_pool));
867
868   noderev = apr_pcalloc(result_pool, sizeof(*noderev));
869
870   /* Read the node-rev id. */
871   value = svn_hash_gets(headers, HEADER_ID);
872   if (value == NULL)
873       /* ### More information: filename/offset coordinates */
874       return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
875                               _("Missing id field in node-rev"));
876
877   SVN_ERR(svn_stream_close(stream));
878
879   SVN_ERR(svn_fs_fs__id_parse(&noderev->id, value, result_pool));
880   noderev_id = value; /* for error messages later */
881
882   /* Read the type. */
883   value = svn_hash_gets(headers, HEADER_TYPE);
884
885   if ((value == NULL) ||
886       (   strcmp(value, SVN_FS_FS__KIND_FILE)
887        && strcmp(value, SVN_FS_FS__KIND_DIR)))
888     /* ### s/kind/type/ */
889     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
890                              _("Missing kind field in node-rev '%s'"),
891                              noderev_id);
892
893   noderev->kind = (strcmp(value, SVN_FS_FS__KIND_FILE) == 0)
894                 ? svn_node_file
895                 : svn_node_dir;
896
897   /* Read the 'count' field. */
898   value = svn_hash_gets(headers, HEADER_COUNT);
899   if (value)
900     SVN_ERR(svn_cstring_atoi(&noderev->predecessor_count, value));
901   else
902     noderev->predecessor_count = 0;
903
904   /* Get the properties location. */
905   value = svn_hash_gets(headers, HEADER_PROPS);
906   if (value)
907     {
908       SVN_ERR(read_rep_offsets(&noderev->prop_rep, value,
909                                noderev->id, result_pool, scratch_pool));
910     }
911
912   /* Get the data location. */
913   value = svn_hash_gets(headers, HEADER_TEXT);
914   if (value)
915     {
916       SVN_ERR(read_rep_offsets(&noderev->data_rep, value,
917                                noderev->id, result_pool, scratch_pool));
918     }
919
920   /* Get the created path. */
921   value = svn_hash_gets(headers, HEADER_CPATH);
922   if (value == NULL)
923     {
924       return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
925                                _("Missing cpath field in node-rev '%s'"),
926                                noderev_id);
927     }
928   else
929     {
930       if (!svn_fspath__is_canonical(value))
931         return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
932                             _("Non-canonical cpath field in node-rev '%s'"),
933                             noderev_id);
934
935       noderev->created_path = apr_pstrdup(result_pool, value);
936     }
937
938   /* Get the predecessor ID. */
939   value = svn_hash_gets(headers, HEADER_PRED);
940   if (value)
941     SVN_ERR(svn_fs_fs__id_parse(&noderev->predecessor_id, value,
942                                 result_pool));
943
944   /* Get the copyroot. */
945   value = svn_hash_gets(headers, HEADER_COPYROOT);
946   if (value == NULL)
947     {
948       noderev->copyroot_path = apr_pstrdup(result_pool, noderev->created_path);
949       noderev->copyroot_rev = svn_fs_fs__id_rev(noderev->id);
950     }
951   else
952     {
953       SVN_ERR(parse_revnum(&noderev->copyroot_rev, (const char **)&value));
954
955       if (!svn_fspath__is_canonical(value))
956         return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
957                                  _("Malformed copyroot line in node-rev '%s'"),
958                                  noderev_id);
959       noderev->copyroot_path = apr_pstrdup(result_pool, value);
960     }
961
962   /* Get the copyfrom. */
963   value = svn_hash_gets(headers, HEADER_COPYFROM);
964   if (value == NULL)
965     {
966       noderev->copyfrom_path = NULL;
967       noderev->copyfrom_rev = SVN_INVALID_REVNUM;
968     }
969   else
970     {
971       SVN_ERR(parse_revnum(&noderev->copyfrom_rev, (const char **)&value));
972
973       if (*value == 0)
974         return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
975                                  _("Malformed copyfrom line in node-rev '%s'"),
976                                  noderev_id);
977       noderev->copyfrom_path = apr_pstrdup(result_pool, value);
978     }
979
980   /* Get whether this is a fresh txn root. */
981   value = svn_hash_gets(headers, HEADER_FRESHTXNRT);
982   noderev->is_fresh_txn_root = (value != NULL);
983
984   /* Get the mergeinfo count. */
985   value = svn_hash_gets(headers, HEADER_MINFO_CNT);
986   if (value)
987     SVN_ERR(svn_cstring_atoi64(&noderev->mergeinfo_count, value));
988   else
989     noderev->mergeinfo_count = 0;
990
991   /* Get whether *this* node has mergeinfo. */
992   value = svn_hash_gets(headers, HEADER_MINFO_HERE);
993   noderev->has_mergeinfo = (value != NULL);
994
995   *noderev_p = noderev;
996
997   return SVN_NO_ERROR;
998 }
999
1000 /* Return a textual representation of the DIGEST of given KIND.
1001  * If IS_NULL is TRUE, no digest is available.
1002  * Allocate the result in RESULT_POOL.
1003  */
1004 static const char *
1005 format_digest(const unsigned char *digest,
1006               svn_checksum_kind_t kind,
1007               svn_boolean_t is_null,
1008               apr_pool_t *result_pool)
1009 {
1010   svn_checksum_t checksum;
1011   checksum.digest = digest;
1012   checksum.kind = kind;
1013
1014   if (is_null)
1015     return "(null)";
1016
1017   return svn_checksum_to_cstring_display(&checksum, result_pool);
1018 }
1019
1020 svn_stringbuf_t *
1021 svn_fs_fs__unparse_representation(representation_t *rep,
1022                                   int format,
1023                                   svn_boolean_t mutable_rep_truncated,
1024                                   apr_pool_t *result_pool,
1025                                   apr_pool_t *scratch_pool)
1026 {
1027   char buffer[SVN_INT64_BUFFER_SIZE];
1028   if (svn_fs_fs__id_txn_used(&rep->txn_id) && mutable_rep_truncated)
1029     return svn_stringbuf_ncreate("-1", 2, result_pool);
1030
1031   if (format < SVN_FS_FS__MIN_REP_SHARING_FORMAT || !rep->has_sha1)
1032     return svn_stringbuf_createf
1033             (result_pool, "%ld %" APR_UINT64_T_FMT " %" SVN_FILESIZE_T_FMT
1034              " %" SVN_FILESIZE_T_FMT " %s",
1035              rep->revision, rep->item_index, rep->size,
1036              rep->expanded_size,
1037              format_digest(rep->md5_digest, svn_checksum_md5, FALSE,
1038                            scratch_pool));
1039
1040   svn__ui64tobase36(buffer, rep->uniquifier.number);
1041   return svn_stringbuf_createf
1042           (result_pool, "%ld %" APR_UINT64_T_FMT " %" SVN_FILESIZE_T_FMT
1043            " %" SVN_FILESIZE_T_FMT " %s %s %s/_%s",
1044            rep->revision, rep->item_index, rep->size,
1045            rep->expanded_size,
1046            format_digest(rep->md5_digest, svn_checksum_md5,
1047                          FALSE, scratch_pool),
1048            format_digest(rep->sha1_digest, svn_checksum_sha1,
1049                          !rep->has_sha1, scratch_pool),
1050            svn_fs_fs__id_txn_unparse(&rep->uniquifier.noderev_txn_id,
1051                                      scratch_pool),
1052            buffer);
1053 }
1054
1055
1056 svn_error_t *
1057 svn_fs_fs__write_noderev(svn_stream_t *outfile,
1058                          node_revision_t *noderev,
1059                          int format,
1060                          svn_boolean_t include_mergeinfo,
1061                          apr_pool_t *scratch_pool)
1062 {
1063   SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_ID ": %s\n",
1064                             svn_fs_fs__id_unparse(noderev->id,
1065                                                   scratch_pool)->data));
1066
1067   SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_TYPE ": %s\n",
1068                             (noderev->kind == svn_node_file) ?
1069                             SVN_FS_FS__KIND_FILE : SVN_FS_FS__KIND_DIR));
1070
1071   if (noderev->predecessor_id)
1072     SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_PRED ": %s\n",
1073                               svn_fs_fs__id_unparse(noderev->predecessor_id,
1074                                                     scratch_pool)->data));
1075
1076   SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COUNT ": %d\n",
1077                             noderev->predecessor_count));
1078
1079   if (noderev->data_rep)
1080     SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_TEXT ": %s\n",
1081                               svn_fs_fs__unparse_representation
1082                                 (noderev->data_rep,
1083                                  format,
1084                                  noderev->kind == svn_node_dir,
1085                                  scratch_pool, scratch_pool)->data));
1086
1087   if (noderev->prop_rep)
1088     SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_PROPS ": %s\n",
1089                               svn_fs_fs__unparse_representation
1090                                 (noderev->prop_rep, format,
1091                                  TRUE, scratch_pool, scratch_pool)->data));
1092
1093   SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_CPATH ": %s\n",
1094                             noderev->created_path));
1095
1096   if (noderev->copyfrom_path)
1097     SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COPYFROM ": %ld"
1098                               " %s\n",
1099                               noderev->copyfrom_rev,
1100                               noderev->copyfrom_path));
1101
1102   if ((noderev->copyroot_rev != svn_fs_fs__id_rev(noderev->id)) ||
1103       (strcmp(noderev->copyroot_path, noderev->created_path) != 0))
1104     SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COPYROOT ": %ld"
1105                               " %s\n",
1106                               noderev->copyroot_rev,
1107                               noderev->copyroot_path));
1108
1109   if (noderev->is_fresh_txn_root)
1110     SVN_ERR(svn_stream_puts(outfile, HEADER_FRESHTXNRT ": y\n"));
1111
1112   if (include_mergeinfo)
1113     {
1114       if (noderev->mergeinfo_count > 0)
1115         SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_MINFO_CNT
1116                                   ": %" APR_INT64_T_FMT "\n",
1117                                   noderev->mergeinfo_count));
1118
1119       if (noderev->has_mergeinfo)
1120         SVN_ERR(svn_stream_puts(outfile, HEADER_MINFO_HERE ": y\n"));
1121     }
1122
1123   return svn_stream_puts(outfile, "\n");
1124 }
1125
1126 svn_error_t *
1127 svn_fs_fs__read_rep_header(svn_fs_fs__rep_header_t **header,
1128                            svn_stream_t *stream,
1129                            apr_pool_t *result_pool,
1130                            apr_pool_t *scratch_pool)
1131 {
1132   svn_stringbuf_t *buffer;
1133   char *str, *last_str;
1134   apr_int64_t val;
1135   svn_boolean_t eol = FALSE;
1136
1137   SVN_ERR(svn_stream_readline(stream, &buffer, "\n", &eol, scratch_pool));
1138
1139   *header = apr_pcalloc(result_pool, sizeof(**header));
1140   (*header)->header_size = buffer->len + 1;
1141   if (strcmp(buffer->data, REP_PLAIN) == 0)
1142     {
1143       (*header)->type = svn_fs_fs__rep_plain;
1144       return SVN_NO_ERROR;
1145     }
1146
1147   if (strcmp(buffer->data, REP_DELTA) == 0)
1148     {
1149       /* This is a delta against the empty stream. */
1150       (*header)->type = svn_fs_fs__rep_self_delta;
1151       return SVN_NO_ERROR;
1152     }
1153
1154   (*header)->type = svn_fs_fs__rep_delta;
1155
1156   /* We have hopefully a DELTA vs. a non-empty base revision. */
1157   last_str = buffer->data;
1158   str = svn_cstring_tokenize(" ", &last_str);
1159   if (! str || (strcmp(str, REP_DELTA) != 0))
1160     goto error;
1161
1162   SVN_ERR(parse_revnum(&(*header)->base_revision, (const char **)&last_str));
1163
1164   str = svn_cstring_tokenize(" ", &last_str);
1165   if (! str)
1166     goto error;
1167   SVN_ERR(svn_cstring_atoi64(&val, str));
1168   (*header)->base_item_index = (apr_off_t)val;
1169
1170   str = svn_cstring_tokenize(" ", &last_str);
1171   if (! str)
1172     goto error;
1173   SVN_ERR(svn_cstring_atoi64(&val, str));
1174   (*header)->base_length = (svn_filesize_t)val;
1175
1176   return SVN_NO_ERROR;
1177
1178  error:
1179   return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
1180                            _("Malformed representation header"));
1181 }
1182
1183 svn_error_t *
1184 svn_fs_fs__write_rep_header(svn_fs_fs__rep_header_t *header,
1185                             svn_stream_t *stream,
1186                             apr_pool_t *scratch_pool)
1187 {
1188   const char *text;
1189
1190   switch (header->type)
1191     {
1192       case svn_fs_fs__rep_plain:
1193         text = REP_PLAIN "\n";
1194         break;
1195
1196       case svn_fs_fs__rep_self_delta:
1197         text = REP_DELTA "\n";
1198         break;
1199
1200       default:
1201         text = apr_psprintf(scratch_pool, REP_DELTA " %ld %" APR_OFF_T_FMT
1202                                           " %" SVN_FILESIZE_T_FMT "\n",
1203                             header->base_revision, header->base_item_index,
1204                             header->base_length);
1205     }
1206
1207   return svn_error_trace(svn_stream_puts(stream, text));
1208 }