low_level.c revision 299742
1/* low_level.c --- low level r/w access to fs_fs file structures
2 *
3 * ====================================================================
4 *    Licensed to the Apache Software Foundation (ASF) under one
5 *    or more contributor license agreements.  See the NOTICE file
6 *    distributed with this work for additional information
7 *    regarding copyright ownership.  The ASF licenses this file
8 *    to you under the Apache License, Version 2.0 (the
9 *    "License"); you may not use this file except in compliance
10 *    with the License.  You may obtain a copy of the License at
11 *
12 *      http://www.apache.org/licenses/LICENSE-2.0
13 *
14 *    Unless required by applicable law or agreed to in writing,
15 *    software distributed under the License is distributed on an
16 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 *    KIND, either express or implied.  See the License for the
18 *    specific language governing permissions and limitations
19 *    under the License.
20 * ====================================================================
21 */
22
23#include "svn_private_config.h"
24#include "svn_hash.h"
25#include "svn_pools.h"
26#include "svn_sorts.h"
27#include "private/svn_sorts_private.h"
28#include "private/svn_string_private.h"
29#include "private/svn_subr_private.h"
30#include "private/svn_fspath.h"
31
32#include "../libsvn_fs/fs-loader.h"
33
34#include "low_level.h"
35
36/* Headers used to describe node-revision in the revision file. */
37#define HEADER_ID          "id"
38#define HEADER_TYPE        "type"
39#define HEADER_COUNT       "count"
40#define HEADER_PROPS       "props"
41#define HEADER_TEXT        "text"
42#define HEADER_CPATH       "cpath"
43#define HEADER_PRED        "pred"
44#define HEADER_COPYFROM    "copyfrom"
45#define HEADER_COPYROOT    "copyroot"
46#define HEADER_FRESHTXNRT  "is-fresh-txn-root"
47#define HEADER_MINFO_HERE  "minfo-here"
48#define HEADER_MINFO_CNT   "minfo-cnt"
49
50/* Kinds that a change can be. */
51#define ACTION_MODIFY      "modify"
52#define ACTION_ADD         "add"
53#define ACTION_DELETE      "delete"
54#define ACTION_REPLACE     "replace"
55#define ACTION_RESET       "reset"
56
57/* True and False flags. */
58#define FLAG_TRUE          "true"
59#define FLAG_FALSE         "false"
60
61/* Kinds of representation. */
62#define REP_PLAIN          "PLAIN"
63#define REP_DELTA          "DELTA"
64
65/* An arbitrary maximum path length, so clients can't run us out of memory
66 * by giving us arbitrarily large paths. */
67#define FSFS_MAX_PATH_LEN 4096
68
69/* The 256 is an arbitrary size large enough to hold the node id and the
70 * various flags. */
71#define MAX_CHANGE_LINE_LEN FSFS_MAX_PATH_LEN + 256
72
73/* Convert the C string in *TEXT to a revision number and return it in *REV.
74 * Overflows, negative values other than -1 and terminating characters other
75 * than 0x20 or 0x0 will cause an error.  Set *TEXT to the first char after
76 * the initial separator or to EOS.
77 */
78static svn_error_t *
79parse_revnum(svn_revnum_t *rev,
80             const char **text)
81{
82  const char *string = *text;
83  if ((string[0] == '-') && (string[1] == '1'))
84    {
85      *rev = SVN_INVALID_REVNUM;
86      string += 2;
87    }
88  else
89    {
90      SVN_ERR(svn_revnum_parse(rev, string, &string));
91    }
92
93  if (*string == ' ')
94    ++string;
95  else if (*string != '\0')
96    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
97                            _("Invalid character in revision number"));
98
99  *text = string;
100  return SVN_NO_ERROR;
101}
102
103svn_error_t *
104svn_fs_fs__parse_revision_trailer(apr_off_t *root_offset,
105                                  apr_off_t *changes_offset,
106                                  svn_stringbuf_t *trailer,
107                                  svn_revnum_t rev)
108{
109  int i, num_bytes;
110  const char *str;
111
112  /* This cast should be safe since the maximum amount read, 64, will
113     never be bigger than the size of an int. */
114  num_bytes = (int) trailer->len;
115
116  /* The last byte should be a newline. */
117  if (trailer->len == 0 || trailer->data[trailer->len - 1] != '\n')
118    {
119      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
120                               _("Revision file (r%ld) lacks trailing newline"),
121                               rev);
122    }
123
124  /* Look for the next previous newline. */
125  for (i = num_bytes - 2; i >= 0; i--)
126    {
127      if (trailer->data[i] == '\n')
128        break;
129    }
130
131  if (i < 0)
132    {
133      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
134                               _("Final line in revision file (r%ld) longer "
135                                 "than 64 characters"),
136                               rev);
137    }
138
139  i++;
140  str = &trailer->data[i];
141
142  /* find the next space */
143  for ( ; i < (num_bytes - 2) ; i++)
144    if (trailer->data[i] == ' ')
145      break;
146
147  if (i == (num_bytes - 2))
148    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
149                             _("Final line in revision file r%ld missing space"),
150                             rev);
151
152  if (root_offset)
153    {
154      apr_int64_t val;
155
156      trailer->data[i] = '\0';
157      SVN_ERR(svn_cstring_atoi64(&val, str));
158      *root_offset = (apr_off_t)val;
159    }
160
161  i++;
162  str = &trailer->data[i];
163
164  /* find the next newline */
165  for ( ; i < num_bytes; i++)
166    if (trailer->data[i] == '\n')
167      break;
168
169  if (changes_offset)
170    {
171      apr_int64_t val;
172
173      trailer->data[i] = '\0';
174      SVN_ERR(svn_cstring_atoi64(&val, str));
175      *changes_offset = (apr_off_t)val;
176    }
177
178  return SVN_NO_ERROR;
179}
180
181svn_stringbuf_t *
182svn_fs_fs__unparse_revision_trailer(apr_off_t root_offset,
183                                    apr_off_t changes_offset,
184                                    apr_pool_t *result_pool)
185{
186  return svn_stringbuf_createf(result_pool,
187                               "%" APR_OFF_T_FMT " %" APR_OFF_T_FMT "\n",
188                               root_offset,
189                               changes_offset);
190}
191
192svn_error_t *
193svn_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
243svn_stringbuf_t *
244svn_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. */
264static svn_error_t *
265read_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
449svn_error_t *
450svn_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
481svn_error_t *
482svn_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. */
511static svn_error_t *
512write_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
591svn_error_t *
592svn_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. */
648static svn_error_t *
649read_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
708svn_error_t *
709svn_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. */
809static svn_error_t *
810read_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
843svn_error_t *
844svn_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 */
992static const char *
993format_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
1008svn_stringbuf_t *
1009svn_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
1044svn_error_t *
1045svn_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
1114svn_error_t *
1115svn_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
1171svn_error_t *
1172svn_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}
1197