1
/* low_level.c --- low level r/w access to fs_fs file structures
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
12
* http://www.apache.org/licenses/LICENSE-2.0
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
20
* ====================================================================
23
#include "svn_private_config.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"
32
#include "../libsvn_fs/fs-loader.h"
34
#include "low_level.h"
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"
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"
57
/* True and False flags. */
58
#define FLAG_TRUE "true"
59
#define FLAG_FALSE "false"
61
/* Kinds of representation. */
62
#define REP_PLAIN "PLAIN"
63
#define REP_DELTA "DELTA"
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
69
/* The 256 is an arbitrary size large enough to hold the node id and the
71
#define MAX_CHANGE_LINE_LEN FSFS_MAX_PATH_LEN + 256
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.
79
parse_revnum(svn_revnum_t *rev,
82
const char *string = *text;
83
if ((string[0] == '-') && (string[1] == '1'))
85
*rev = SVN_INVALID_REVNUM;
90
SVN_ERR(svn_revnum_parse(rev, string, &string));
95
else if (*string != '\0')
96
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
97
_("Invalid character in revision number"));
104
svn_fs_fs__parse_revision_trailer(apr_off_t *root_offset,
105
apr_off_t *changes_offset,
106
svn_stringbuf_t *trailer,
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;
116
/* The last byte should be a newline. */
117
if (trailer->len == 0 || trailer->data[trailer->len - 1] != '\n')
119
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
120
_("Revision file (r%ld) lacks trailing newline"),
124
/* Look for the next previous newline. */
125
for (i = num_bytes - 2; i >= 0; i--)
127
if (trailer->data[i] == '\n')
133
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
134
_("Final line in revision file (r%ld) longer "
135
"than 64 characters"),
140
str = &trailer->data[i];
142
/* find the next space */
143
for ( ; i < (num_bytes - 2) ; i++)
144
if (trailer->data[i] == ' ')
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"),
156
trailer->data[i] = '\0';
157
SVN_ERR(svn_cstring_atoi64(&val, str));
158
*root_offset = (apr_off_t)val;
162
str = &trailer->data[i];
164
/* find the next newline */
165
for ( ; i < num_bytes; i++)
166
if (trailer->data[i] == '\n')
173
trailer->data[i] = '\0';
174
SVN_ERR(svn_cstring_atoi64(&val, str));
175
*changes_offset = (apr_off_t)val;
182
svn_fs_fs__unparse_revision_trailer(apr_off_t root_offset,
183
apr_off_t changes_offset,
184
apr_pool_t *result_pool)
186
return svn_stringbuf_createf(result_pool,
187
"%" APR_OFF_T_FMT " %" APR_OFF_T_FMT "\n",
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,
199
apr_pool_t *result_pool)
202
char *last_str = footer->data;
204
/* Get the L2P offset. */
205
const char *str = svn_cstring_tokenize(" ", &last_str);
207
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
208
_("Invalid revision footer"));
210
SVN_ERR(svn_cstring_atoi64(&val, str));
211
*l2p_offset = (apr_off_t)val;
213
/* Get the L2P checksum. */
214
str = svn_cstring_tokenize(" ", &last_str);
216
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
217
_("Invalid revision footer"));
219
SVN_ERR(svn_checksum_parse_hex(l2p_checksum, svn_checksum_md5, str,
222
/* Get the P2L offset. */
223
str = svn_cstring_tokenize(" ", &last_str);
225
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
226
_("Invalid revision footer"));
228
SVN_ERR(svn_cstring_atoi64(&val, str));
229
*p2l_offset = (apr_off_t)val;
231
/* Get the P2L checksum. */
232
str = svn_cstring_tokenize(" ", &last_str);
234
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
235
_("Invalid revision footer"));
237
SVN_ERR(svn_checksum_parse_hex(p2l_checksum, svn_checksum_md5, str,
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)
251
return svn_stringbuf_createf(result_pool,
252
"%" APR_OFF_T_FMT " %s %" APR_OFF_T_FMT " %s",
254
svn_checksum_to_cstring(l2p_checksum,
257
svn_checksum_to_cstring(p2l_checksum,
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. */
265
read_change(change_t **change_p,
266
svn_stream_t *stream,
267
apr_pool_t *result_pool,
268
apr_pool_t *scratch_pool)
270
svn_stringbuf_t *line;
271
svn_boolean_t eof = TRUE;
273
char *str, *last_str, *kind_str;
274
svn_fs_path_change2_t *info;
276
/* Default return value. */
279
SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, scratch_pool));
281
/* Check for a blank line. */
282
if (eof || (line->len == 0))
285
change = apr_pcalloc(result_pool, sizeof(*change));
286
info = &change->info;
287
last_str = line->data;
289
/* Get the node-id of the change. */
290
str = svn_cstring_tokenize(" ", &last_str);
292
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
293
_("Invalid changes line in rev-file"));
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"));
300
/* Get the change type. */
301
str = svn_cstring_tokenize(" ", &last_str);
303
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
304
_("Invalid changes line in rev-file"));
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, '-');
312
/* Cap off the end of "str" (the action). */
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;
320
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
321
_("Invalid changes line in rev-file"));
324
if (strcmp(str, ACTION_MODIFY) == 0)
326
info->change_kind = svn_fs_path_change_modify;
328
else if (strcmp(str, ACTION_ADD) == 0)
330
info->change_kind = svn_fs_path_change_add;
332
else if (strcmp(str, ACTION_DELETE) == 0)
334
info->change_kind = svn_fs_path_change_delete;
336
else if (strcmp(str, ACTION_REPLACE) == 0)
338
info->change_kind = svn_fs_path_change_replace;
340
else if (strcmp(str, ACTION_RESET) == 0)
342
info->change_kind = svn_fs_path_change_reset;
346
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
347
_("Invalid change kind in rev file"));
350
/* Get the text-mod flag. */
351
str = svn_cstring_tokenize(" ", &last_str);
353
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
354
_("Invalid changes line in rev-file"));
356
if (strcmp(str, FLAG_TRUE) == 0)
358
info->text_mod = TRUE;
360
else if (strcmp(str, FLAG_FALSE) == 0)
362
info->text_mod = FALSE;
366
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
367
_("Invalid text-mod flag in rev-file"));
370
/* Get the prop-mod flag. */
371
str = svn_cstring_tokenize(" ", &last_str);
373
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
374
_("Invalid changes line in rev-file"));
376
if (strcmp(str, FLAG_TRUE) == 0)
378
info->prop_mod = TRUE;
380
else if (strcmp(str, FLAG_FALSE) == 0)
382
info->prop_mod = FALSE;
386
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
387
_("Invalid prop-mod flag in rev-file"));
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 != '/')
396
str = svn_cstring_tokenize(" ", &last_str);
398
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
399
_("Invalid changes line in rev-file"));
401
if (strcmp(str, FLAG_TRUE) == 0)
403
info->mergeinfo_mod = svn_tristate_true;
405
else if (strcmp(str, FLAG_FALSE) == 0)
407
info->mergeinfo_mod = svn_tristate_false;
411
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
412
_("Invalid mergeinfo-mod flag in rev-file"));
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"));
421
change->path.len = strlen(last_str);
422
change->path.data = apr_pstrdup(result_pool, last_str);
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)
429
info->copyfrom_rev = SVN_INVALID_REVNUM;
430
info->copyfrom_path = NULL;
434
last_str = line->data;
435
SVN_ERR(parse_revnum(&info->copyfrom_rev, (const char **)&last_str));
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"));
441
info->copyfrom_path = apr_pstrdup(result_pool, last_str);
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)
456
apr_pool_t *iterpool;
458
/* Pre-allocate enough room for most change lists.
459
(will be auto-expanded as necessary).
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).
466
*changes = apr_array_make(result_pool, 63, sizeof(change_t *));
468
SVN_ERR(read_change(&change, stream, result_pool, scratch_pool));
469
iterpool = svn_pool_create(scratch_pool);
472
APR_ARRAY_PUSH(*changes, change_t*) = change;
473
SVN_ERR(read_change(&change, stream, result_pool, iterpool));
474
svn_pool_clear(iterpool);
476
svn_pool_destroy(iterpool);
482
svn_fs_fs__read_changes_incrementally(svn_stream_t *stream,
483
svn_fs_fs__change_receiver_t
485
void *change_receiver_baton,
486
apr_pool_t *scratch_pool)
489
apr_pool_t *iterpool;
491
iterpool = svn_pool_create(scratch_pool);
494
svn_pool_clear(iterpool);
496
SVN_ERR(read_change(&change, stream, iterpool, iterpool));
498
SVN_ERR(change_receiver(change_receiver_baton, change, iterpool));
501
svn_pool_destroy(iterpool);
506
/* Write a single change entry, path PATH, change CHANGE, to STREAM.
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. */
512
write_change_entry(svn_stream_t *stream,
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)
520
const char *change_string = NULL;
521
const char *kind_string = "";
522
const char *mergeinfo_string = "";
523
svn_stringbuf_t *buf;
526
switch (change->change_kind)
528
case svn_fs_path_change_modify:
529
change_string = ACTION_MODIFY;
531
case svn_fs_path_change_add:
532
change_string = ACTION_ADD;
534
case svn_fs_path_change_delete:
535
change_string = ACTION_DELETE;
537
case svn_fs_path_change_replace:
538
change_string = ACTION_REPLACE;
540
case svn_fs_path_change_reset:
541
change_string = ACTION_RESET;
544
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
545
_("Invalid change type %d"),
546
change->change_kind);
549
if (change->node_rev_id)
550
idstr = svn_fs_fs__id_unparse(change->node_rev_id, scratch_pool)->data;
552
idstr = ACTION_RESET;
554
if (include_node_kind)
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);
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
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,
577
if (SVN_IS_VALID_REVNUM(change->copyfrom_rev))
579
svn_stringbuf_appendcstr(buf, apr_psprintf(scratch_pool, "%ld %s",
580
change->copyfrom_rev,
581
change->copyfrom_path));
584
svn_stringbuf_appendbyte(buf, '\n');
586
/* Write all change info in one write call. */
588
return svn_error_trace(svn_stream_write(stream, buf->data, &len));
592
svn_fs_fs__write_changes(svn_stream_t *stream,
595
svn_boolean_t terminate_list,
596
apr_pool_t *scratch_pool)
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;
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.
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.
616
sorted_changed_paths = svn_sort__hash(changes,
617
svn_sort_compare_items_lexically,
620
/* Write all items to disk in the new order. */
621
for (i = 0; i < sorted_changed_paths->nelts; ++i)
623
svn_fs_path_change2_t *change;
626
svn_pool_clear(iterpool);
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;
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));
637
svn_stream_puts(stream, "\n");
639
svn_pool_destroy(iterpool);
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
649
read_header_block(apr_hash_t **headers,
650
svn_stream_t *stream,
651
apr_pool_t *result_pool)
653
*headers = svn_hash__make(result_pool);
657
svn_stringbuf_t *header_str;
658
const char *name, *value;
663
SVN_ERR(svn_stream_readline(stream, &header_str, "\n", &eof,
666
if (eof || header_str->len == 0)
667
break; /* end of header block */
669
while (header_str->data[i] != ':')
671
if (header_str->data[i] == '\0')
672
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
673
_("Found malformed header '%s' in "
679
/* Create a 'name' string and point to it. */
680
header_str->data[i] = '\0';
681
name = header_str->data;
684
/* Check if we have enough data to parse. */
685
if (i + 2 > header_str->len)
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 "
695
/* Skip over the NULL byte and the space following it. */
698
value = header_str->data + i;
700
/* header_str is safely in our pool, so we can use bits of it as
702
apr_hash_set(*headers, name, name_len, value);
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)
714
representation_t *rep;
717
char *string = text->data;
718
svn_checksum_t *checksum;
721
rep = apr_pcalloc(result_pool, sizeof(*rep));
724
SVN_ERR(parse_revnum(&rep->revision, (const char **)&string));
726
/* initialize transaction info (never stored) */
727
svn_fs_fs__id_txn_reset(&rep->txn_id);
729
/* while in transactions, it is legal to simply write "-1" */
730
str = svn_cstring_tokenize(" ", &string);
733
if (rep->revision == SVN_INVALID_REVNUM)
736
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
737
_("Malformed text representation offset line in node-rev"));
740
SVN_ERR(svn_cstring_atoi64(&val, str));
741
rep->item_index = (apr_uint64_t)val;
743
str = svn_cstring_tokenize(" ", &string);
745
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
746
_("Malformed text representation offset line in node-rev"));
748
SVN_ERR(svn_cstring_atoi64(&val, str));
749
rep->size = (svn_filesize_t)val;
751
str = svn_cstring_tokenize(" ", &string);
753
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
754
_("Malformed text representation offset line in node-rev"));
756
SVN_ERR(svn_cstring_atoi64(&val, str));
757
rep->expanded_size = (svn_filesize_t)val;
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"));
765
SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_md5, str,
767
memcpy(rep->md5_digest, checksum->digest, sizeof(rep->md5_digest));
769
/* The remaining fields are only used for formats >= 4, so check that. */
770
str = svn_cstring_tokenize(" ", &string);
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"));
779
SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_sha1, str,
781
rep->has_sha1 = checksum != NULL;
782
memcpy(rep->sha1_digest, checksum->digest, sizeof(rep->sha1_digest));
784
/* Read the uniquifier. */
785
str = svn_cstring_tokenize("/", &string);
787
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
788
_("Malformed text representation offset line in node-rev"));
790
SVN_ERR(svn_fs_fs__id_txn_parse(&rep->uniquifier.noderev_txn_id, str));
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"));
798
rep->uniquifier.number = svn__base36toui64(&end, str);
801
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
802
_("Malformed text representation offset line in node-rev"));
807
/* Wrap svn_fs_fs__parse_representation(), extracting its TXN_ID from our
808
NODEREV_ID, and adding an error message. */
810
read_rep_offsets(representation_t **rep_p,
812
const svn_fs_id_t *noderev_id,
813
apr_pool_t *result_pool,
814
apr_pool_t *scratch_pool)
817
= svn_fs_fs__parse_representation(rep_p,
818
svn_stringbuf_create_wrap(string,
824
const svn_string_t *id_unparsed;
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)");
833
return svn_error_quick_wrap(err, where);
836
if ((*rep_p)->revision == SVN_INVALID_REVNUM)
838
(*rep_p)->txn_id = *svn_fs_fs__id_txn_id(noderev_id);
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)
850
node_revision_t *noderev;
852
const char *noderev_id;
854
SVN_ERR(read_header_block(&headers, stream, scratch_pool));
856
noderev = apr_pcalloc(result_pool, sizeof(*noderev));
858
/* Read the node-rev id. */
859
value = svn_hash_gets(headers, HEADER_ID);
861
/* ### More information: filename/offset coordinates */
862
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
863
_("Missing id field in node-rev"));
865
SVN_ERR(svn_stream_close(stream));
867
SVN_ERR(svn_fs_fs__id_parse(&noderev->id, value, result_pool));
868
noderev_id = value; /* for error messages later */
871
value = svn_hash_gets(headers, HEADER_TYPE);
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'"),
881
noderev->kind = (strcmp(value, SVN_FS_FS__KIND_FILE) == 0)
885
/* Read the 'count' field. */
886
value = svn_hash_gets(headers, HEADER_COUNT);
888
SVN_ERR(svn_cstring_atoi(&noderev->predecessor_count, value));
890
noderev->predecessor_count = 0;
892
/* Get the properties location. */
893
value = svn_hash_gets(headers, HEADER_PROPS);
896
SVN_ERR(read_rep_offsets(&noderev->prop_rep, value,
897
noderev->id, result_pool, scratch_pool));
900
/* Get the data location. */
901
value = svn_hash_gets(headers, HEADER_TEXT);
904
SVN_ERR(read_rep_offsets(&noderev->data_rep, value,
905
noderev->id, result_pool, scratch_pool));
908
/* Get the created path. */
909
value = svn_hash_gets(headers, HEADER_CPATH);
912
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
913
_("Missing cpath field in node-rev '%s'"),
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'"),
923
noderev->created_path = apr_pstrdup(result_pool, value);
926
/* Get the predecessor ID. */
927
value = svn_hash_gets(headers, HEADER_PRED);
929
SVN_ERR(svn_fs_fs__id_parse(&noderev->predecessor_id, value,
932
/* Get the copyroot. */
933
value = svn_hash_gets(headers, HEADER_COPYROOT);
936
noderev->copyroot_path = apr_pstrdup(result_pool, noderev->created_path);
937
noderev->copyroot_rev = svn_fs_fs__id_rev(noderev->id);
941
SVN_ERR(parse_revnum(&noderev->copyroot_rev, (const char **)&value));
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'"),
947
noderev->copyroot_path = apr_pstrdup(result_pool, value);
950
/* Get the copyfrom. */
951
value = svn_hash_gets(headers, HEADER_COPYFROM);
954
noderev->copyfrom_path = NULL;
955
noderev->copyfrom_rev = SVN_INVALID_REVNUM;
959
SVN_ERR(parse_revnum(&noderev->copyfrom_rev, (const char **)&value));
962
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
963
_("Malformed copyfrom line in node-rev '%s'"),
965
noderev->copyfrom_path = apr_pstrdup(result_pool, value);
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);
972
/* Get the mergeinfo count. */
973
value = svn_hash_gets(headers, HEADER_MINFO_CNT);
975
SVN_ERR(svn_cstring_atoi64(&noderev->mergeinfo_count, value));
977
noderev->mergeinfo_count = 0;
979
/* Get whether *this* node has mergeinfo. */
980
value = svn_hash_gets(headers, HEADER_MINFO_HERE);
981
noderev->has_mergeinfo = (value != NULL);
983
*noderev_p = noderev;
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.
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)
998
svn_checksum_t checksum;
999
checksum.digest = digest;
1000
checksum.kind = kind;
1005
return svn_checksum_to_cstring_display(&checksum, result_pool);
1009
svn_fs_fs__unparse_representation(representation_t *rep,
1011
svn_boolean_t mutable_rep_truncated,
1012
apr_pool_t *result_pool,
1013
apr_pool_t *scratch_pool)
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);
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,
1025
format_digest(rep->md5_digest, svn_checksum_md5, FALSE,
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,
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,
1045
svn_fs_fs__write_noderev(svn_stream_t *outfile,
1046
node_revision_t *noderev,
1048
svn_boolean_t include_mergeinfo,
1049
apr_pool_t *scratch_pool)
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));
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));
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));
1064
SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COUNT ": %d\n",
1065
noderev->predecessor_count));
1067
if (noderev->data_rep)
1068
SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_TEXT ": %s\n",
1069
svn_fs_fs__unparse_representation
1072
noderev->kind == svn_node_dir,
1073
scratch_pool, scratch_pool)->data));
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));
1081
SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_CPATH ": %s\n",
1082
noderev->created_path));
1084
if (noderev->copyfrom_path)
1085
SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COPYFROM ": %ld"
1087
noderev->copyfrom_rev,
1088
noderev->copyfrom_path));
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"
1094
noderev->copyroot_rev,
1095
noderev->copyroot_path));
1097
if (noderev->is_fresh_txn_root)
1098
SVN_ERR(svn_stream_puts(outfile, HEADER_FRESHTXNRT ": y\n"));
1100
if (include_mergeinfo)
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));
1107
if (noderev->has_mergeinfo)
1108
SVN_ERR(svn_stream_puts(outfile, HEADER_MINFO_HERE ": y\n"));
1111
return svn_stream_puts(outfile, "\n");
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)
1120
svn_stringbuf_t *buffer;
1121
char *str, *last_str;
1123
svn_boolean_t eol = FALSE;
1125
SVN_ERR(svn_stream_readline(stream, &buffer, "\n", &eol, scratch_pool));
1127
*header = apr_pcalloc(result_pool, sizeof(**header));
1128
(*header)->header_size = buffer->len + 1;
1129
if (strcmp(buffer->data, REP_PLAIN) == 0)
1131
(*header)->type = svn_fs_fs__rep_plain;
1132
return SVN_NO_ERROR;
1135
if (strcmp(buffer->data, REP_DELTA) == 0)
1137
/* This is a delta against the empty stream. */
1138
(*header)->type = svn_fs_fs__rep_self_delta;
1139
return SVN_NO_ERROR;
1142
(*header)->type = svn_fs_fs__rep_delta;
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))
1150
SVN_ERR(parse_revnum(&(*header)->base_revision, (const char **)&last_str));
1152
str = svn_cstring_tokenize(" ", &last_str);
1155
SVN_ERR(svn_cstring_atoi64(&val, str));
1156
(*header)->base_item_index = (apr_off_t)val;
1158
str = svn_cstring_tokenize(" ", &last_str);
1161
SVN_ERR(svn_cstring_atoi64(&val, str));
1162
(*header)->base_length = (svn_filesize_t)val;
1164
return SVN_NO_ERROR;
1167
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
1168
_("Malformed representation header"));
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)
1178
switch (header->type)
1180
case svn_fs_fs__rep_plain:
1181
text = REP_PLAIN "\n";
1184
case svn_fs_fs__rep_self_delta:
1185
text = REP_DELTA "\n";
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);
1195
return svn_error_trace(svn_stream_puts(stream, text));