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