~ubuntu-branches/debian/sid/subversion/sid

« back to all changes in this revision

Viewing changes to subversion/libsvn_fs_fs/low_level.c

  • Committer: Package Import Robot
  • Author(s): James McCoy
  • Date: 2015-08-07 21:32:47 UTC
  • mfrom: (0.2.15) (4.1.7 experimental)
  • Revision ID: package-import@ubuntu.com-20150807213247-ozyewtmgsr6tkewl
Tags: 1.9.0-1
* Upload to unstable
* New upstream release.
  + Security fixes
    - CVE-2015-3184: Mixed anonymous/authenticated path-based authz with
      httpd 2.4
    - CVE-2015-3187: svn_repos_trace_node_locations() reveals paths hidden
      by authz
* Add >= 2.7 requirement for python-all-dev Build-Depends, needed to run
  tests.
* Remove Build-Conflicts against ruby-test-unit.  (Closes: #791844)
* Remove patches/apache_module_dependency in favor of expressing the
  dependencies in authz_svn.load/dav_svn.load.
* Build-Depend on apache2-dev (>= 2.4.16) to ensure ap_some_authn_required()
  is available when building mod_authz_svn and Depend on apache2-bin (>=
  2.4.16) for runtime support.

Show diffs side-by-side

added added

removed removed

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