low_level.c revision 362181
1/* low_level.c --- low level r/w access to FSX 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#include "util.h"
36#include "pack.h"
37#include "cached_data.h"
38
39/* Headers used to describe node-revision in the revision file. */
40#define HEADER_ID          "id"
41#define HEADER_NODE        "node"
42#define HEADER_COPY        "copy"
43#define HEADER_TYPE        "type"
44#define HEADER_COUNT       "count"
45#define HEADER_PROPS       "props"
46#define HEADER_TEXT        "text"
47#define HEADER_CPATH       "cpath"
48#define HEADER_PRED        "pred"
49#define HEADER_COPYFROM    "copyfrom"
50#define HEADER_COPYROOT    "copyroot"
51#define HEADER_MINFO_HERE  "minfo-here"
52#define HEADER_MINFO_CNT   "minfo-cnt"
53
54/* Kinds that a change can be. */
55#define ACTION_MODIFY      "modify"
56#define ACTION_ADD         "add"
57#define ACTION_DELETE      "delete"
58#define ACTION_REPLACE     "replace"
59
60/* True and False flags. */
61#define FLAG_TRUE          "true"
62#define FLAG_FALSE         "false"
63
64/* Kinds of representation. */
65#define REP_DELTA          "DELTA"
66
67/* An arbitrary maximum path length, so clients can't run us out of memory
68 * by giving us arbitrarily large paths. */
69#define FSX_MAX_PATH_LEN 4096
70
71/* The 256 is an arbitrary size large enough to hold the node id and the
72 * various flags. */
73#define MAX_CHANGE_LINE_LEN FSX_MAX_PATH_LEN + 256
74
75/* Convert the C string in *TEXT to a revision number and return it in *REV.
76 * Overflows, negative values other than -1 and terminating characters other
77 * than 0x20 or 0x0 will cause an error.  Set *TEXT to the first char after
78 * the initial separator or to EOS.
79 */
80static svn_error_t *
81parse_revnum(svn_revnum_t *rev,
82             const char **text)
83{
84  const char *string = *text;
85  if ((string[0] == '-') && (string[1] == '1'))
86    {
87      *rev = SVN_INVALID_REVNUM;
88      string += 2;
89    }
90  else
91    {
92      SVN_ERR(svn_revnum_parse(rev, string, &string));
93    }
94
95  if (*string == ' ')
96    ++string;
97  else if (*string != '\0')
98    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
99                            _("Invalid character in revision number"));
100
101  *text = string;
102  return SVN_NO_ERROR;
103}
104
105/* If ERR is not NULL, wrap it MESSAGE.  The latter must have an %ld
106 * format parameter that will be filled with REV. */
107static svn_error_t *
108wrap_footer_error(svn_error_t *err,
109                  const char *message,
110                  svn_revnum_t rev)
111{
112  if (err)
113    return svn_error_quick_wrapf(err, message, rev);
114
115  return SVN_NO_ERROR;
116}
117
118svn_error_t *
119svn_fs_x__parse_footer(apr_off_t *l2p_offset,
120                       svn_checksum_t **l2p_checksum,
121                       apr_off_t *p2l_offset,
122                       svn_checksum_t **p2l_checksum,
123                       svn_stringbuf_t *footer,
124                       svn_revnum_t rev,
125                       apr_off_t footer_offset,
126                       apr_pool_t *result_pool)
127{
128  apr_int64_t val;
129  char *last_str = footer->data;
130
131  /* Get the L2P offset. */
132  const char *str = svn_cstring_tokenize(" ", &last_str);
133  if (str == NULL)
134    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
135                             "Invalid r%ld footer", rev);
136
137  SVN_ERR(wrap_footer_error(svn_cstring_strtoi64(&val, str, 0,
138                                                 footer_offset - 1, 10),
139                            "Invalid L2P offset in r%ld footer",
140                            rev));
141  *l2p_offset = (apr_off_t)val;
142
143  /* Get the L2P checksum. */
144  str = svn_cstring_tokenize(" ", &last_str);
145  if (str == NULL)
146    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
147                             "Invalid r%ld footer", rev);
148
149  SVN_ERR(svn_checksum_parse_hex(l2p_checksum, svn_checksum_md5, str,
150                                 result_pool));
151
152  /* Get the P2L offset. */
153  str = svn_cstring_tokenize(" ", &last_str);
154  if (str == NULL)
155    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
156                             "Invalid r%ld footer", rev);
157
158  SVN_ERR(wrap_footer_error(svn_cstring_strtoi64(&val, str, 0,
159                                                 footer_offset - 1, 10),
160                            "Invalid P2L offset in r%ld footer",
161                            rev));
162  *p2l_offset = (apr_off_t)val;
163
164  /* The P2L indes follows the L2P index */
165  if (*p2l_offset <= *l2p_offset)
166    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
167                             "P2L offset %s must be larger than L2P offset %s"
168                             " in r%ld footer",
169                             apr_psprintf(result_pool,
170                                          "0x%" APR_UINT64_T_HEX_FMT,
171                                          (apr_uint64_t)*p2l_offset),
172                             apr_psprintf(result_pool,
173                                          "0x%" APR_UINT64_T_HEX_FMT,
174                                          (apr_uint64_t)*l2p_offset),
175                             rev);
176
177  /* Get the P2L checksum. */
178  str = svn_cstring_tokenize(" ", &last_str);
179  if (str == NULL)
180    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
181                             "Invalid r%ld footer", rev);
182
183  SVN_ERR(svn_checksum_parse_hex(p2l_checksum, svn_checksum_md5, str,
184                                 result_pool));
185
186  return SVN_NO_ERROR;
187}
188
189svn_stringbuf_t *
190svn_fs_x__unparse_footer(apr_off_t l2p_offset,
191                         svn_checksum_t *l2p_checksum,
192                         apr_off_t p2l_offset,
193                         svn_checksum_t *p2l_checksum,
194                         apr_pool_t *result_pool,
195                         apr_pool_t *scratch_pool)
196{
197  return svn_stringbuf_createf(result_pool,
198                               "%" APR_OFF_T_FMT " %s %" APR_OFF_T_FMT " %s",
199                               l2p_offset,
200                               svn_checksum_to_cstring(l2p_checksum,
201                                                       scratch_pool),
202                               p2l_offset,
203                               svn_checksum_to_cstring(p2l_checksum,
204                                                       scratch_pool));
205}
206
207/* Given a revision file FILE that has been pre-positioned at the
208   beginning of a Node-Rev header block, read in that header block and
209   store it in the apr_hash_t HEADERS.  All allocations will be from
210   RESULT_POOL. */
211static svn_error_t *
212read_header_block(apr_hash_t **headers,
213                  svn_stream_t *stream,
214                  apr_pool_t *result_pool)
215{
216  *headers = svn_hash__make(result_pool);
217
218  while (1)
219    {
220      svn_stringbuf_t *header_str;
221      const char *name, *value;
222      apr_size_t i = 0;
223      apr_size_t name_len;
224      svn_boolean_t eof;
225
226      SVN_ERR(svn_stream_readline(stream, &header_str, "\n", &eof,
227                                  result_pool));
228
229      if (eof || header_str->len == 0)
230        break; /* end of header block */
231
232      while (header_str->data[i] != ':')
233        {
234          if (header_str->data[i] == '\0')
235            return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
236                                     _("Found malformed header '%s' in "
237                                       "revision file"),
238                                     header_str->data);
239          i++;
240        }
241
242      /* Create a 'name' string and point to it. */
243      header_str->data[i] = '\0';
244      name = header_str->data;
245      name_len = i;
246
247      /* Check if we have enough data to parse. */
248      if (i + 2 > header_str->len)
249        {
250          /* Restore the original line for the error. */
251          header_str->data[i] = ':';
252          return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
253                                   _("Found malformed header '%s' in "
254                                     "revision file"),
255                                   header_str->data);
256        }
257
258      /* Skip over the NULL byte and the space following it. */
259      i += 2;
260
261      value = header_str->data + i;
262
263      /* header_str is safely in our pool, so we can use bits of it as
264         key and value. */
265      apr_hash_set(*headers, name, name_len, value);
266    }
267
268  return SVN_NO_ERROR;
269}
270
271svn_error_t *
272svn_fs_x__parse_representation(svn_fs_x__representation_t **rep_p,
273                               svn_stringbuf_t *text,
274                               apr_pool_t *result_pool,
275                               apr_pool_t *scratch_pool)
276{
277  svn_fs_x__representation_t *rep;
278  char *str;
279  apr_int64_t val;
280  char *string = text->data;
281  svn_checksum_t *checksum;
282
283  rep = apr_pcalloc(result_pool, sizeof(*rep));
284  *rep_p = rep;
285
286  str = svn_cstring_tokenize(" ", &string);
287  if (str == NULL)
288    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
289                            _("Malformed text representation offset line in node-rev"));
290
291  SVN_ERR(svn_cstring_atoi64(&rep->id.change_set, str));
292
293  /* while in transactions, it is legal to simply write "-1" */
294  if (rep->id.change_set == -1)
295    return SVN_NO_ERROR;
296
297  str = svn_cstring_tokenize(" ", &string);
298  if (str == NULL)
299    {
300      if (rep->id.change_set == SVN_FS_X__INVALID_CHANGE_SET)
301        return SVN_NO_ERROR;
302
303      return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
304                              _("Malformed text representation offset line in node-rev"));
305    }
306
307  SVN_ERR(svn_cstring_atoi64(&val, str));
308  rep->id.number = (apr_off_t)val;
309
310  str = svn_cstring_tokenize(" ", &string);
311  if (str == NULL)
312    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
313                            _("Malformed text representation offset line in node-rev"));
314
315  SVN_ERR(svn_cstring_atoi64(&val, str));
316  rep->size = (svn_filesize_t)val;
317
318  str = svn_cstring_tokenize(" ", &string);
319  if (str == NULL)
320    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
321                            _("Malformed text representation offset line in node-rev"));
322
323  SVN_ERR(svn_cstring_atoi64(&val, str));
324  rep->expanded_size = (svn_filesize_t)val;
325
326  /* Read in the MD5 hash. */
327  str = svn_cstring_tokenize(" ", &string);
328  if ((str == NULL) || (strlen(str) != (APR_MD5_DIGESTSIZE * 2)))
329    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
330                            _("Malformed text representation offset line in node-rev"));
331
332  SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_md5, str,
333                                 scratch_pool));
334  if (checksum)
335    memcpy(rep->md5_digest, checksum->digest, sizeof(rep->md5_digest));
336
337  /* The remaining fields are only used for formats >= 4, so check that. */
338  str = svn_cstring_tokenize(" ", &string);
339  if (str == NULL)
340    return SVN_NO_ERROR;
341
342  /* Read the SHA1 hash. */
343  if (strlen(str) != (APR_SHA1_DIGESTSIZE * 2))
344    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
345                            _("Malformed text representation offset line in node-rev"));
346
347  SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_sha1, str,
348                                 scratch_pool));
349  rep->has_sha1 = checksum != NULL;
350  if (checksum)
351    memcpy(rep->sha1_digest, checksum->digest, sizeof(rep->sha1_digest));
352
353  return SVN_NO_ERROR;
354}
355
356/* Wrap read_rep_offsets_body(), extracting its TXN_ID from our NODEREV_ID,
357   and adding an error message. */
358static svn_error_t *
359read_rep_offsets(svn_fs_x__representation_t **rep_p,
360                 char *string,
361                 const svn_fs_x__id_t *noderev_id,
362                 apr_pool_t *result_pool,
363                 apr_pool_t *scratch_pool)
364{
365  svn_error_t *err
366    = svn_fs_x__parse_representation(rep_p,
367                                     svn_stringbuf_create_wrap(string,
368                                                               scratch_pool),
369                                     result_pool,
370                                     scratch_pool);
371  if (err)
372    {
373      const svn_string_t *id_unparsed;
374      const char *where;
375
376      id_unparsed = svn_fs_x__id_unparse(noderev_id, scratch_pool);
377      where = apr_psprintf(scratch_pool,
378                           _("While reading representation offsets "
379                             "for node-revision '%s':"),
380                           id_unparsed->data);
381
382      return svn_error_quick_wrap(err, where);
383    }
384
385  return SVN_NO_ERROR;
386}
387
388/* If PATH needs to be escaped, return an escaped version of it, allocated
389 * from RESULT_POOL. Otherwise, return PATH directly. */
390static const char *
391auto_escape_path(const char *path,
392                 apr_pool_t *result_pool)
393{
394  apr_size_t len = strlen(path);
395  apr_size_t i;
396  const char esc = '\x1b';
397
398  for (i = 0; i < len; ++i)
399    if (path[i] < ' ')
400      {
401        svn_stringbuf_t *escaped = svn_stringbuf_create_ensure(2 * len,
402                                                               result_pool);
403        for (i = 0; i < len; ++i)
404          if (path[i] < ' ')
405            {
406              svn_stringbuf_appendbyte(escaped, esc);
407              svn_stringbuf_appendbyte(escaped, path[i] + 'A' - 1);
408            }
409          else
410            {
411              svn_stringbuf_appendbyte(escaped, path[i]);
412            }
413
414        return escaped->data;
415      }
416
417   return path;
418}
419
420/* If PATH has been escaped, return the un-escaped version of it, allocated
421 * from RESULT_POOL. Otherwise, return PATH directly. */
422static const char *
423auto_unescape_path(const char *path,
424                   apr_pool_t *result_pool)
425{
426  const char esc = '\x1b';
427  if (strchr(path, esc))
428    {
429      apr_size_t len = strlen(path);
430      apr_size_t i;
431
432      svn_stringbuf_t *unescaped = svn_stringbuf_create_ensure(len,
433                                                               result_pool);
434      for (i = 0; i < len; ++i)
435        if (path[i] == esc)
436          svn_stringbuf_appendbyte(unescaped, path[++i] + 1 - 'A');
437        else
438          svn_stringbuf_appendbyte(unescaped, path[i]);
439
440      return unescaped->data;
441    }
442
443   return path;
444}
445
446/* Find entry HEADER_NAME in HEADERS and parse its value into *ID. */
447static svn_error_t *
448read_id_part(svn_fs_x__id_t *id,
449             apr_hash_t *headers,
450             const char *header_name)
451{
452  const char *value = svn_hash_gets(headers, header_name);
453  if (value == NULL)
454    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
455                             _("Missing %s field in node-rev"),
456                             header_name);
457
458  SVN_ERR(svn_fs_x__id_parse(id, value));
459  return SVN_NO_ERROR;
460}
461
462svn_error_t *
463svn_fs_x__read_noderev(svn_fs_x__noderev_t **noderev_p,
464                       svn_stream_t *stream,
465                       apr_pool_t *result_pool,
466                       apr_pool_t *scratch_pool)
467{
468  apr_hash_t *headers;
469  svn_fs_x__noderev_t *noderev;
470  char *value;
471  const char *noderev_id;
472
473  SVN_ERR(read_header_block(&headers, stream, scratch_pool));
474  SVN_ERR(svn_stream_close(stream));
475
476  noderev = apr_pcalloc(result_pool, sizeof(*noderev));
477
478  /* for error messages later */
479  noderev_id = svn_hash_gets(headers, HEADER_ID);
480
481  /* Read the node-rev id. */
482  SVN_ERR(read_id_part(&noderev->noderev_id, headers, HEADER_ID));
483  SVN_ERR(read_id_part(&noderev->node_id, headers, HEADER_NODE));
484  SVN_ERR(read_id_part(&noderev->copy_id, headers, HEADER_COPY));
485
486  /* Read the type. */
487  value = svn_hash_gets(headers, HEADER_TYPE);
488
489  if ((value == NULL) ||
490      (   strcmp(value, SVN_FS_X__KIND_FILE)
491       && strcmp(value, SVN_FS_X__KIND_DIR)))
492    /* ### s/kind/type/ */
493    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
494                             _("Missing kind field in node-rev '%s'"),
495                             noderev_id);
496
497  noderev->kind = (strcmp(value, SVN_FS_X__KIND_FILE) == 0)
498                ? svn_node_file
499                : svn_node_dir;
500
501  /* Read the 'count' field. */
502  value = svn_hash_gets(headers, HEADER_COUNT);
503  if (value)
504    SVN_ERR(svn_cstring_atoi(&noderev->predecessor_count, value));
505  else
506    noderev->predecessor_count = 0;
507
508  /* Get the properties location. */
509  value = svn_hash_gets(headers, HEADER_PROPS);
510  if (value)
511    {
512      SVN_ERR(read_rep_offsets(&noderev->prop_rep, value,
513                               &noderev->noderev_id, result_pool,
514                               scratch_pool));
515    }
516
517  /* Get the data location. */
518  value = svn_hash_gets(headers, HEADER_TEXT);
519  if (value)
520    {
521      SVN_ERR(read_rep_offsets(&noderev->data_rep, value,
522                               &noderev->noderev_id, result_pool,
523                               scratch_pool));
524    }
525
526  /* Get the created path. */
527  value = svn_hash_gets(headers, HEADER_CPATH);
528  if (value == NULL)
529    {
530      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
531                               _("Missing cpath field in node-rev '%s'"),
532                               noderev_id);
533    }
534  else
535    {
536      if (!svn_fspath__is_canonical(value))
537        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
538                            _("Non-canonical cpath field in node-rev '%s'"),
539                            noderev_id);
540
541      noderev->created_path = auto_unescape_path(apr_pstrdup(result_pool,
542                                                              value),
543                                                 result_pool);
544    }
545
546  /* Get the predecessor ID. */
547  value = svn_hash_gets(headers, HEADER_PRED);
548  if (value)
549    SVN_ERR(svn_fs_x__id_parse(&noderev->predecessor_id, value));
550  else
551    svn_fs_x__id_reset(&noderev->predecessor_id);
552
553  /* Get the copyroot. */
554  value = svn_hash_gets(headers, HEADER_COPYROOT);
555  if (value == NULL)
556    {
557      noderev->copyroot_path = noderev->created_path;
558      noderev->copyroot_rev
559        = svn_fs_x__get_revnum(noderev->noderev_id.change_set);
560    }
561  else
562    {
563      SVN_ERR(parse_revnum(&noderev->copyroot_rev, (const char **)&value));
564
565      if (!svn_fspath__is_canonical(value))
566        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
567                                 _("Malformed copyroot line in node-rev '%s'"),
568                                 noderev_id);
569      noderev->copyroot_path = auto_unescape_path(apr_pstrdup(result_pool,
570                                                              value),
571                                                  result_pool);
572    }
573
574  /* Get the copyfrom. */
575  value = svn_hash_gets(headers, HEADER_COPYFROM);
576  if (value == NULL)
577    {
578      noderev->copyfrom_path = NULL;
579      noderev->copyfrom_rev = SVN_INVALID_REVNUM;
580    }
581  else
582    {
583      SVN_ERR(parse_revnum(&noderev->copyfrom_rev, (const char **)&value));
584
585      if (*value == 0)
586        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
587                                 _("Malformed copyfrom line in node-rev '%s'"),
588                                 noderev_id);
589      noderev->copyfrom_path = auto_unescape_path(apr_pstrdup(result_pool,
590                                                              value),
591                                                  result_pool);
592    }
593
594  /* Get the mergeinfo count. */
595  value = svn_hash_gets(headers, HEADER_MINFO_CNT);
596  if (value)
597    SVN_ERR(svn_cstring_atoi64(&noderev->mergeinfo_count, value));
598  else
599    noderev->mergeinfo_count = 0;
600
601  /* Get whether *this* node has mergeinfo. */
602  value = svn_hash_gets(headers, HEADER_MINFO_HERE);
603  noderev->has_mergeinfo = (value != NULL);
604
605  *noderev_p = noderev;
606
607  return SVN_NO_ERROR;
608}
609
610/* Return a textual representation of the DIGEST of given KIND.
611 * If IS_NULL is TRUE, no digest is available.
612 * Allocate the result in RESULT_POOL.
613 */
614static const char *
615format_digest(const unsigned char *digest,
616              svn_checksum_kind_t kind,
617              svn_boolean_t is_null,
618              apr_pool_t *result_pool)
619{
620  svn_checksum_t checksum;
621  checksum.digest = digest;
622  checksum.kind = kind;
623
624  if (is_null)
625    return "(null)";
626
627  return svn_checksum_to_cstring_display(&checksum, result_pool);
628}
629
630svn_stringbuf_t *
631svn_fs_x__unparse_representation(svn_fs_x__representation_t *rep,
632                                 svn_boolean_t mutable_rep_truncated,
633                                 apr_pool_t *result_pool,
634                                 apr_pool_t *scratch_pool)
635{
636  if (!rep->has_sha1)
637    return svn_stringbuf_createf
638            (result_pool,
639             "%" APR_INT64_T_FMT " %" APR_UINT64_T_FMT " %" SVN_FILESIZE_T_FMT
640             " %" SVN_FILESIZE_T_FMT " %s",
641             rep->id.change_set, rep->id.number, rep->size,
642             rep->expanded_size,
643             format_digest(rep->md5_digest, svn_checksum_md5, FALSE,
644                           scratch_pool));
645
646  return svn_stringbuf_createf
647          (result_pool,
648           "%" APR_INT64_T_FMT " %" APR_UINT64_T_FMT " %" SVN_FILESIZE_T_FMT
649           " %" SVN_FILESIZE_T_FMT " %s %s",
650           rep->id.change_set, rep->id.number, rep->size,
651           rep->expanded_size,
652           format_digest(rep->md5_digest, svn_checksum_md5,
653                         FALSE, scratch_pool),
654           format_digest(rep->sha1_digest, svn_checksum_sha1,
655                         !rep->has_sha1, scratch_pool));
656}
657
658
659svn_error_t *
660svn_fs_x__write_noderev(svn_stream_t *outfile,
661                        svn_fs_x__noderev_t *noderev,
662                        apr_pool_t *scratch_pool)
663{
664  svn_string_t *str_id;
665
666  str_id = svn_fs_x__id_unparse(&noderev->noderev_id, scratch_pool);
667  SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_ID ": %s\n",
668                            str_id->data));
669  str_id = svn_fs_x__id_unparse(&noderev->node_id, scratch_pool);
670  SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_NODE ": %s\n",
671                            str_id->data));
672  str_id = svn_fs_x__id_unparse(&noderev->copy_id, scratch_pool);
673  SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COPY ": %s\n",
674                            str_id->data));
675
676  SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_TYPE ": %s\n",
677                            (noderev->kind == svn_node_file) ?
678                            SVN_FS_X__KIND_FILE : SVN_FS_X__KIND_DIR));
679
680  if (svn_fs_x__id_used(&noderev->predecessor_id))
681    SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_PRED ": %s\n",
682                              svn_fs_x__id_unparse(&noderev->predecessor_id,
683                                                   scratch_pool)->data));
684
685  SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COUNT ": %d\n",
686                            noderev->predecessor_count));
687
688  if (noderev->data_rep)
689    SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_TEXT ": %s\n",
690                              svn_fs_x__unparse_representation
691                                (noderev->data_rep,
692                                 noderev->kind == svn_node_dir,
693                                 scratch_pool, scratch_pool)->data));
694
695  if (noderev->prop_rep)
696    SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_PROPS ": %s\n",
697                              svn_fs_x__unparse_representation
698                                (noderev->prop_rep,
699                                 TRUE, scratch_pool, scratch_pool)->data));
700
701  SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_CPATH ": %s\n",
702                            auto_escape_path(noderev->created_path,
703                                             scratch_pool)));
704
705  if (noderev->copyfrom_path)
706    SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COPYFROM ": %ld"
707                              " %s\n",
708                              noderev->copyfrom_rev,
709                              auto_escape_path(noderev->copyfrom_path,
710                                               scratch_pool)));
711
712  if (   (   noderev->copyroot_rev
713           != svn_fs_x__get_revnum(noderev->noderev_id.change_set))
714      || (strcmp(noderev->copyroot_path, noderev->created_path) != 0))
715    SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COPYROOT ": %ld"
716                              " %s\n",
717                              noderev->copyroot_rev,
718                              auto_escape_path(noderev->copyroot_path,
719                                               scratch_pool)));
720
721  if (noderev->mergeinfo_count > 0)
722    SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_MINFO_CNT ": %"
723                              APR_INT64_T_FMT "\n",
724                              noderev->mergeinfo_count));
725
726  if (noderev->has_mergeinfo)
727    SVN_ERR(svn_stream_puts(outfile, HEADER_MINFO_HERE ": y\n"));
728
729  return svn_stream_puts(outfile, "\n");
730}
731
732svn_error_t *
733svn_fs_x__read_rep_header(svn_fs_x__rep_header_t **header,
734                          svn_stream_t *stream,
735                          apr_pool_t *result_pool,
736                          apr_pool_t *scratch_pool)
737{
738  svn_stringbuf_t *buffer;
739  char *str, *last_str;
740  apr_int64_t val;
741  svn_boolean_t eol = FALSE;
742
743  SVN_ERR(svn_stream_readline(stream, &buffer, "\n", &eol, scratch_pool));
744
745  *header = apr_pcalloc(result_pool, sizeof(**header));
746  (*header)->header_size = buffer->len + 1;
747  if (strcmp(buffer->data, REP_DELTA) == 0)
748    {
749      /* This is a delta against the empty stream. */
750      (*header)->type = svn_fs_x__rep_self_delta;
751      return SVN_NO_ERROR;
752    }
753
754  (*header)->type = svn_fs_x__rep_delta;
755
756  /* We have hopefully a DELTA vs. a non-empty base revision. */
757  last_str = buffer->data;
758  str = svn_cstring_tokenize(" ", &last_str);
759  if (! str || (strcmp(str, REP_DELTA) != 0))
760    goto error;
761
762  SVN_ERR(parse_revnum(&(*header)->base_revision, (const char **)&last_str));
763
764  str = svn_cstring_tokenize(" ", &last_str);
765  if (! str)
766    goto error;
767  SVN_ERR(svn_cstring_atoi64(&val, str));
768  (*header)->base_item_index = (apr_off_t)val;
769
770  str = svn_cstring_tokenize(" ", &last_str);
771  if (! str)
772    goto error;
773  SVN_ERR(svn_cstring_atoi64(&val, str));
774  (*header)->base_length = (svn_filesize_t)val;
775
776  return SVN_NO_ERROR;
777
778 error:
779  return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
780                           _("Malformed representation header"));
781}
782
783svn_error_t *
784svn_fs_x__write_rep_header(svn_fs_x__rep_header_t *header,
785                           svn_stream_t *stream,
786                           apr_pool_t *scratch_pool)
787{
788  const char *text;
789
790  switch (header->type)
791    {
792      case svn_fs_x__rep_self_delta:
793        text = REP_DELTA "\n";
794        break;
795
796      default:
797        text = apr_psprintf(scratch_pool, REP_DELTA " %ld %" APR_OFF_T_FMT
798                                          " %" SVN_FILESIZE_T_FMT "\n",
799                            header->base_revision, header->base_item_index,
800                            header->base_length);
801    }
802
803  return svn_error_trace(svn_stream_puts(stream, text));
804}
805
806/* Read the next entry in the changes record from file FILE and store
807   the resulting change in *CHANGE_P.  If there is no next record,
808   store NULL there.  Perform all allocations from POOL. */
809static svn_error_t *
810read_change(svn_fs_x__change_t **change_p,
811            svn_stream_t *stream,
812            apr_pool_t *result_pool,
813            apr_pool_t *scratch_pool)
814{
815  svn_stringbuf_t *line;
816  svn_boolean_t eof = TRUE;
817  svn_fs_x__change_t *change;
818  char *str, *last_str, *kind_str;
819
820  /* Default return value. */
821  *change_p = NULL;
822
823  SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, scratch_pool));
824
825  /* Check for a blank line. */
826  if (eof || (line->len == 0))
827    return SVN_NO_ERROR;
828
829  change = apr_pcalloc(result_pool, sizeof(*change));
830  last_str = line->data;
831
832  /* Get the change type. */
833  str = svn_cstring_tokenize(" ", &last_str);
834  if (str == NULL)
835    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
836                            _("Invalid changes line in rev-file"));
837
838  /* Don't bother to check the format number before looking for
839   * node-kinds: just read them if you find them. */
840  change->node_kind = svn_node_unknown;
841  kind_str = strchr(str, '-');
842  if (kind_str)
843    {
844      /* Cap off the end of "str" (the action). */
845      *kind_str = '\0';
846      kind_str++;
847      if (strcmp(kind_str, SVN_FS_X__KIND_FILE) == 0)
848        change->node_kind = svn_node_file;
849      else if (strcmp(kind_str, SVN_FS_X__KIND_DIR) == 0)
850        change->node_kind = svn_node_dir;
851      else
852        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
853                                _("Invalid changes line in rev-file"));
854    }
855
856  if (strcmp(str, ACTION_MODIFY) == 0)
857    {
858      change->change_kind = svn_fs_path_change_modify;
859    }
860  else if (strcmp(str, ACTION_ADD) == 0)
861    {
862      change->change_kind = svn_fs_path_change_add;
863    }
864  else if (strcmp(str, ACTION_DELETE) == 0)
865    {
866      change->change_kind = svn_fs_path_change_delete;
867    }
868  else if (strcmp(str, ACTION_REPLACE) == 0)
869    {
870      change->change_kind = svn_fs_path_change_replace;
871    }
872  else
873    {
874      return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
875                              _("Invalid change kind in rev file"));
876    }
877
878  /* Get the text-mod flag. */
879  str = svn_cstring_tokenize(" ", &last_str);
880  if (str == NULL)
881    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
882                            _("Invalid changes line in rev-file"));
883
884  if (strcmp(str, FLAG_TRUE) == 0)
885    {
886      change->text_mod = TRUE;
887    }
888  else if (strcmp(str, FLAG_FALSE) == 0)
889    {
890      change->text_mod = FALSE;
891    }
892  else
893    {
894      return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
895                              _("Invalid text-mod flag in rev-file"));
896    }
897
898  /* Get the prop-mod flag. */
899  str = svn_cstring_tokenize(" ", &last_str);
900  if (str == NULL)
901    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
902                            _("Invalid changes line in rev-file"));
903
904  if (strcmp(str, FLAG_TRUE) == 0)
905    {
906      change->prop_mod = TRUE;
907    }
908  else if (strcmp(str, FLAG_FALSE) == 0)
909    {
910      change->prop_mod = FALSE;
911    }
912  else
913    {
914      return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
915                              _("Invalid prop-mod flag in rev-file"));
916    }
917
918  /* Get the mergeinfo-mod flag. */
919  str = svn_cstring_tokenize(" ", &last_str);
920  if (str == NULL)
921    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
922                            _("Invalid changes line in rev-file"));
923
924  if (strcmp(str, FLAG_TRUE) == 0)
925    {
926      change->mergeinfo_mod = svn_tristate_true;
927    }
928  else if (strcmp(str, FLAG_FALSE) == 0)
929    {
930      change->mergeinfo_mod = svn_tristate_false;
931    }
932  else
933    {
934      return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
935                              _("Invalid mergeinfo-mod flag in rev-file"));
936    }
937
938  /* Get the changed path. */
939  if (!svn_fspath__is_canonical(last_str))
940    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
941                            _("Invalid path in changes line"));
942
943  change->path.data = auto_unescape_path(apr_pstrmemdup(result_pool,
944                                                        last_str,
945                                                        strlen(last_str)),
946                                         result_pool);
947  change->path.len = strlen(change->path.data);
948
949  /* Read the next line, the copyfrom line. */
950  SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, result_pool));
951  change->copyfrom_known = TRUE;
952  if (eof || line->len == 0)
953    {
954      change->copyfrom_rev = SVN_INVALID_REVNUM;
955      change->copyfrom_path = NULL;
956    }
957  else
958    {
959      last_str = line->data;
960      SVN_ERR(parse_revnum(&change->copyfrom_rev, (const char **)&last_str));
961
962      if (!svn_fspath__is_canonical(last_str))
963        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
964                                _("Invalid copy-from path in changes line"));
965
966      change->copyfrom_path = auto_unescape_path(last_str, result_pool);
967    }
968
969  *change_p = change;
970
971  return SVN_NO_ERROR;
972}
973
974svn_error_t *
975svn_fs_x__read_changes(apr_array_header_t **changes,
976                       svn_stream_t *stream,
977                       int max_count,
978                       apr_pool_t *result_pool,
979                       apr_pool_t *scratch_pool)
980{
981  apr_pool_t *iterpool;
982
983  /* Pre-allocate enough room for most change lists.
984     (will be auto-expanded as necessary).
985
986     Chose the default to just below 2^N such that the doubling reallocs
987     will request roughly 2^M bytes from the OS without exceeding the
988     respective two-power by just a few bytes (leaves room array and APR
989     node overhead for large enough M).
990   */
991  *changes = apr_array_make(result_pool, 63, sizeof(svn_fs_x__change_t *));
992
993  iterpool = svn_pool_create(scratch_pool);
994  for (; max_count > 0; --max_count)
995    {
996      svn_fs_x__change_t *change;
997      svn_pool_clear(iterpool);
998      SVN_ERR(read_change(&change, stream, result_pool, iterpool));
999      if (!change)
1000        break;
1001
1002      APR_ARRAY_PUSH(*changes, svn_fs_x__change_t*) = change;
1003    }
1004  svn_pool_destroy(iterpool);
1005
1006  return SVN_NO_ERROR;
1007}
1008
1009svn_error_t *
1010svn_fs_x__read_changes_incrementally(svn_stream_t *stream,
1011                                     svn_fs_x__change_receiver_t
1012                                       change_receiver,
1013                                     void *change_receiver_baton,
1014                                     apr_pool_t *scratch_pool)
1015{
1016  svn_fs_x__change_t *change;
1017  apr_pool_t *iterpool;
1018
1019  iterpool = svn_pool_create(scratch_pool);
1020  do
1021    {
1022      svn_pool_clear(iterpool);
1023
1024      SVN_ERR(read_change(&change, stream, iterpool, iterpool));
1025      if (change)
1026        SVN_ERR(change_receiver(change_receiver_baton, change, iterpool));
1027    }
1028  while (change);
1029  svn_pool_destroy(iterpool);
1030
1031  return SVN_NO_ERROR;
1032}
1033
1034/* Write a single change entry, path PATH, change CHANGE, to STREAM.
1035
1036   All temporary allocations are in SCRATCH_POOL. */
1037static svn_error_t *
1038write_change_entry(svn_stream_t *stream,
1039                   svn_fs_x__change_t *change,
1040                   apr_pool_t *scratch_pool)
1041{
1042  const char *change_string = NULL;
1043  const char *kind_string = "";
1044  svn_stringbuf_t *buf;
1045  apr_size_t len;
1046
1047  switch (change->change_kind)
1048    {
1049    case svn_fs_path_change_modify:
1050      change_string = ACTION_MODIFY;
1051      break;
1052    case svn_fs_path_change_add:
1053      change_string = ACTION_ADD;
1054      break;
1055    case svn_fs_path_change_delete:
1056      change_string = ACTION_DELETE;
1057      break;
1058    case svn_fs_path_change_replace:
1059      change_string = ACTION_REPLACE;
1060      break;
1061    default:
1062      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
1063                               _("Invalid change type %d"),
1064                               change->change_kind);
1065    }
1066
1067  SVN_ERR_ASSERT(change->node_kind == svn_node_dir
1068                 || change->node_kind == svn_node_file);
1069  kind_string = apr_psprintf(scratch_pool, "-%s",
1070                             change->node_kind == svn_node_dir
1071                             ? SVN_FS_X__KIND_DIR
1072                             : SVN_FS_X__KIND_FILE);
1073
1074  buf = svn_stringbuf_createf(scratch_pool, "%s%s %s %s %s %s\n",
1075                              change_string, kind_string,
1076                              change->text_mod ? FLAG_TRUE : FLAG_FALSE,
1077                              change->prop_mod ? FLAG_TRUE : FLAG_FALSE,
1078                              change->mergeinfo_mod == svn_tristate_true
1079                                               ? FLAG_TRUE : FLAG_FALSE,
1080                              auto_escape_path(change->path.data, scratch_pool));
1081
1082  if (SVN_IS_VALID_REVNUM(change->copyfrom_rev))
1083    {
1084      svn_stringbuf_appendcstr(buf, apr_psprintf(scratch_pool, "%ld %s",
1085                               change->copyfrom_rev,
1086                               auto_escape_path(change->copyfrom_path,
1087                                                scratch_pool)));
1088    }
1089
1090  svn_stringbuf_appendbyte(buf, '\n');
1091
1092  /* Write all change info in one write call. */
1093  len = buf->len;
1094  return svn_error_trace(svn_stream_write(stream, buf->data, &len));
1095}
1096
1097svn_error_t *
1098svn_fs_x__write_changes(svn_stream_t *stream,
1099                        svn_fs_t *fs,
1100                        apr_hash_t *changes,
1101                        svn_boolean_t terminate_list,
1102                        apr_pool_t *scratch_pool)
1103{
1104  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1105  apr_array_header_t *sorted_changed_paths;
1106  int i;
1107
1108  /* For the sake of the repository administrator sort the changes so
1109     that the final file is deterministic and repeatable, however the
1110     rest of the FSX code doesn't require any particular order here.
1111
1112     Also, this sorting is only effective in writing all entries with
1113     a single call as write_final_changed_path_info() does.  For the
1114     list being written incrementally during transaction, we actually
1115     *must not* change the order of entries from different calls.
1116   */
1117  sorted_changed_paths = svn_sort__hash(changes,
1118                                        svn_sort_compare_items_lexically,
1119                                        scratch_pool);
1120
1121  /* Write all items to disk in the new order. */
1122  for (i = 0; i < sorted_changed_paths->nelts; ++i)
1123    {
1124      svn_fs_x__change_t *change;
1125
1126      svn_pool_clear(iterpool);
1127      change = APR_ARRAY_IDX(sorted_changed_paths, i, svn_sort__item_t).value;
1128
1129      /* Write out the new entry into the final rev-file. */
1130      SVN_ERR(write_change_entry(stream, change, iterpool));
1131    }
1132
1133  if (terminate_list)
1134    SVN_ERR(svn_stream_puts(stream, "\n"));
1135
1136  svn_pool_destroy(iterpool);
1137
1138  return SVN_NO_ERROR;
1139}
1140
1141svn_error_t *
1142svn_fs_x__parse_properties(apr_hash_t **properties,
1143                           const svn_string_t *content,
1144                           apr_pool_t *result_pool)
1145{
1146  const apr_byte_t *p = (const apr_byte_t *)content->data;
1147  const apr_byte_t *end = p + content->len;
1148  apr_uint64_t count;
1149
1150  *properties = apr_hash_make(result_pool);
1151
1152  /* Extract the number of properties we are expected to read. */
1153  p = svn__decode_uint(&count, p, end);
1154
1155  /* Read all the properties we find.
1156     Because prop-name and prop-value are nicely NUL-terminated
1157     sub-strings of CONTENT, we can simply reference them there.
1158     I.e. there is no need to copy them around.
1159   */
1160  while (p < end)
1161    {
1162      apr_uint64_t value_len;
1163      svn_string_t *value;
1164
1165      const char *key = (const char *)p;
1166
1167      /* Note that this may never overflow / segfault because
1168         CONTENT itself is NUL-terminated. */
1169      apr_size_t key_len = strlen(key);
1170      p += key_len + 1;
1171      if (key[key_len])
1172        return svn_error_createf(SVN_ERR_FS_CORRUPT_PROPLIST, NULL,
1173                                 "Property name not NUL terminated");
1174
1175      if (p >= end)
1176        return svn_error_createf(SVN_ERR_FS_CORRUPT_PROPLIST, NULL,
1177                                 "Property value missing");
1178      p = svn__decode_uint(&value_len, p, end);
1179      if (value_len >= (end - p))
1180        return svn_error_createf(SVN_ERR_FS_CORRUPT_PROPLIST, NULL,
1181                                 "Property value too long");
1182
1183      value = apr_pcalloc(result_pool, sizeof(*value));
1184      value->data = (const char *)p;
1185      value->len = (apr_size_t)value_len;
1186      if (p[value->len])
1187        return svn_error_createf(SVN_ERR_FS_CORRUPT_PROPLIST, NULL,
1188                                 "Property value not NUL terminated");
1189
1190      p += value->len + 1;
1191
1192      apr_hash_set(*properties, key, key_len, value);
1193    }
1194
1195  /* Check that we read the expected number of properties. */
1196  if ((apr_uint64_t)apr_hash_count(*properties) != count)
1197    return svn_error_createf(SVN_ERR_FS_CORRUPT_PROPLIST, NULL,
1198                             "Property count mismatch");
1199
1200  return SVN_NO_ERROR;
1201}
1202
1203svn_error_t *
1204svn_fs_x__write_properties(svn_stream_t *stream,
1205                           apr_hash_t *proplist,
1206                           apr_pool_t *scratch_pool)
1207{
1208  apr_byte_t buffer[SVN__MAX_ENCODED_UINT_LEN];
1209  apr_size_t len;
1210  apr_hash_index_t *hi;
1211
1212  /* Write the number of properties in this list. */
1213  len = svn__encode_uint(buffer, apr_hash_count(proplist)) - buffer;
1214  SVN_ERR(svn_stream_write(stream, (const char *)buffer, &len));
1215
1216  /* Serialize each property as follows:
1217     <Prop-name> <NUL>
1218     <Value-len> <Prop-value> <NUL>
1219   */
1220  for (hi = apr_hash_first(scratch_pool, proplist);
1221       hi;
1222       hi = apr_hash_next(hi))
1223    {
1224      const char *key;
1225      apr_size_t key_len;
1226      svn_string_t *value;
1227      apr_hash_this(hi, (const void **)&key, (apr_ssize_t *)&key_len,
1228                    (void **)&value);
1229
1230      /* Include the terminating NUL. */
1231      ++key_len;
1232      SVN_ERR(svn_stream_write(stream, key, &key_len));
1233
1234      len = svn__encode_uint(buffer, value->len) - buffer;
1235      SVN_ERR(svn_stream_write(stream, (const char *)buffer, &len));
1236      SVN_ERR(svn_stream_write(stream, value->data, &value->len));
1237
1238      /* Terminate with NUL. */
1239      len = 1;
1240      SVN_ERR(svn_stream_write(stream, "", &len));
1241    }
1242
1243  return SVN_NO_ERROR;
1244}
1245