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
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. */
821static svn_error_t *
822read_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
855svn_error_t *
856svn_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 */
1004static const char *
1005format_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
1020svn_stringbuf_t *
1021svn_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
1056svn_error_t *
1057svn_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
1126svn_error_t *
1127svn_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
1183svn_error_t *
1184svn_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}
1209