1 /* low_level.c --- low level r/w access to FSX 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"
37 #include "cached_data.h"
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"
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"
60 /* True and False flags. */
61 #define FLAG_TRUE "true"
62 #define FLAG_FALSE "false"
64 /* Kinds of representation. */
65 #define REP_DELTA "DELTA"
67 /* An arbitrary maximum path length, so clients can't run us out of memory
68 * by giving us arbitrarily large paths. */
69 #define FSX_MAX_PATH_LEN 4096
71 /* The 256 is an arbitrary size large enough to hold the node id and the
73 #define MAX_CHANGE_LINE_LEN FSX_MAX_PATH_LEN + 256
75 /* Convert the C string in *TEXT to a revision number and return it in *REV.
76 * Overflows, negative values other than -1 and terminating characters other
77 * than 0x20 or 0x0 will cause an error. Set *TEXT to the first char after
78 * the initial separator or to EOS.
81 parse_revnum(svn_revnum_t *rev,
84 const char *string = *text;
85 if ((string[0] == '-') && (string[1] == '1'))
87 *rev = SVN_INVALID_REVNUM;
92 SVN_ERR(svn_revnum_parse(rev, string, &string));
97 else if (*string != '\0')
98 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
99 _("Invalid character in revision number"));
105 /* If ERR is not NULL, wrap it MESSAGE. The latter must have an %ld
106 * format parameter that will be filled with REV. */
108 wrap_footer_error(svn_error_t *err,
113 return svn_error_quick_wrapf(err, message, rev);
119 svn_fs_x__parse_footer(apr_off_t *l2p_offset,
120 svn_checksum_t **l2p_checksum,
121 apr_off_t *p2l_offset,
122 svn_checksum_t **p2l_checksum,
123 svn_stringbuf_t *footer,
125 apr_off_t footer_offset,
126 apr_pool_t *result_pool)
129 char *last_str = footer->data;
131 /* Get the L2P offset. */
132 const char *str = svn_cstring_tokenize(" ", &last_str);
134 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
135 "Invalid r%ld footer", rev);
137 SVN_ERR(wrap_footer_error(svn_cstring_strtoi64(&val, str, 0,
138 footer_offset - 1, 10),
139 "Invalid L2P offset in r%ld footer",
141 *l2p_offset = (apr_off_t)val;
143 /* Get the L2P checksum. */
144 str = svn_cstring_tokenize(" ", &last_str);
146 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
147 "Invalid r%ld footer", rev);
149 SVN_ERR(svn_checksum_parse_hex(l2p_checksum, svn_checksum_md5, str,
152 /* Get the P2L offset. */
153 str = svn_cstring_tokenize(" ", &last_str);
155 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
156 "Invalid r%ld footer", rev);
158 SVN_ERR(wrap_footer_error(svn_cstring_strtoi64(&val, str, 0,
159 footer_offset - 1, 10),
160 "Invalid P2L offset in r%ld footer",
162 *p2l_offset = (apr_off_t)val;
164 /* The P2L indes follows the L2P index */
165 if (*p2l_offset <= *l2p_offset)
166 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
167 "P2L offset %s must be larger than L2P offset %s"
169 apr_psprintf(result_pool,
170 "%" APR_UINT64_T_HEX_FMT,
171 (apr_uint64_t)*p2l_offset),
172 apr_psprintf(result_pool,
173 "%" APR_UINT64_T_HEX_FMT,
174 (apr_uint64_t)*l2p_offset),
177 /* Get the P2L checksum. */
178 str = svn_cstring_tokenize(" ", &last_str);
180 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
181 "Invalid r%ld footer", rev);
183 SVN_ERR(svn_checksum_parse_hex(p2l_checksum, svn_checksum_md5, str,
190 svn_fs_x__unparse_footer(apr_off_t l2p_offset,
191 svn_checksum_t *l2p_checksum,
192 apr_off_t p2l_offset,
193 svn_checksum_t *p2l_checksum,
194 apr_pool_t *result_pool,
195 apr_pool_t *scratch_pool)
197 return svn_stringbuf_createf(result_pool,
198 "%" APR_OFF_T_FMT " %s %" APR_OFF_T_FMT " %s",
200 svn_checksum_to_cstring(l2p_checksum,
203 svn_checksum_to_cstring(p2l_checksum,
207 /* Given a revision file FILE that has been pre-positioned at the
208 beginning of a Node-Rev header block, read in that header block and
209 store it in the apr_hash_t HEADERS. All allocations will be from
212 read_header_block(apr_hash_t **headers,
213 svn_stream_t *stream,
214 apr_pool_t *result_pool)
216 *headers = svn_hash__make(result_pool);
220 svn_stringbuf_t *header_str;
221 const char *name, *value;
226 SVN_ERR(svn_stream_readline(stream, &header_str, "\n", &eof,
229 if (eof || header_str->len == 0)
230 break; /* end of header block */
232 while (header_str->data[i] != ':')
234 if (header_str->data[i] == '\0')
235 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
236 _("Found malformed header '%s' in "
242 /* Create a 'name' string and point to it. */
243 header_str->data[i] = '\0';
244 name = header_str->data;
247 /* Check if we have enough data to parse. */
248 if (i + 2 > header_str->len)
250 /* Restore the original line for the error. */
251 header_str->data[i] = ':';
252 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
253 _("Found malformed header '%s' in "
258 /* Skip over the NULL byte and the space following it. */
261 value = header_str->data + i;
263 /* header_str is safely in our pool, so we can use bits of it as
265 apr_hash_set(*headers, name, name_len, value);
272 svn_fs_x__parse_representation(svn_fs_x__representation_t **rep_p,
273 svn_stringbuf_t *text,
274 apr_pool_t *result_pool,
275 apr_pool_t *scratch_pool)
277 svn_fs_x__representation_t *rep;
280 char *string = text->data;
281 svn_checksum_t *checksum;
283 rep = apr_pcalloc(result_pool, sizeof(*rep));
286 str = svn_cstring_tokenize(" ", &string);
288 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
289 _("Malformed text representation offset line in node-rev"));
291 SVN_ERR(svn_cstring_atoi64(&rep->id.change_set, str));
293 /* while in transactions, it is legal to simply write "-1" */
294 if (rep->id.change_set == -1)
297 str = svn_cstring_tokenize(" ", &string);
300 if (rep->id.change_set == SVN_FS_X__INVALID_CHANGE_SET)
303 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
304 _("Malformed text representation offset line in node-rev"));
307 SVN_ERR(svn_cstring_atoi64(&val, str));
308 rep->id.number = (apr_off_t)val;
310 str = svn_cstring_tokenize(" ", &string);
312 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
313 _("Malformed text representation offset line in node-rev"));
315 SVN_ERR(svn_cstring_atoi64(&val, str));
316 rep->size = (svn_filesize_t)val;
318 str = svn_cstring_tokenize(" ", &string);
320 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
321 _("Malformed text representation offset line in node-rev"));
323 SVN_ERR(svn_cstring_atoi64(&val, str));
324 rep->expanded_size = (svn_filesize_t)val;
326 /* Read in the MD5 hash. */
327 str = svn_cstring_tokenize(" ", &string);
328 if ((str == NULL) || (strlen(str) != (APR_MD5_DIGESTSIZE * 2)))
329 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
330 _("Malformed text representation offset line in node-rev"));
332 SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_md5, str,
335 memcpy(rep->md5_digest, checksum->digest, sizeof(rep->md5_digest));
337 /* The remaining fields are only used for formats >= 4, so check that. */
338 str = svn_cstring_tokenize(" ", &string);
342 /* Read the SHA1 hash. */
343 if (strlen(str) != (APR_SHA1_DIGESTSIZE * 2))
344 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
345 _("Malformed text representation offset line in node-rev"));
347 SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_sha1, str,
349 rep->has_sha1 = checksum != NULL;
351 memcpy(rep->sha1_digest, checksum->digest, sizeof(rep->sha1_digest));
356 /* Wrap read_rep_offsets_body(), extracting its TXN_ID from our NODEREV_ID,
357 and adding an error message. */
359 read_rep_offsets(svn_fs_x__representation_t **rep_p,
361 const svn_fs_x__id_t *noderev_id,
362 apr_pool_t *result_pool,
363 apr_pool_t *scratch_pool)
366 = svn_fs_x__parse_representation(rep_p,
367 svn_stringbuf_create_wrap(string,
373 const svn_string_t *id_unparsed;
376 id_unparsed = svn_fs_x__id_unparse(noderev_id, scratch_pool);
377 where = apr_psprintf(scratch_pool,
378 _("While reading representation offsets "
379 "for node-revision '%s':"),
382 return svn_error_quick_wrap(err, where);
388 /* If PATH needs to be escaped, return an escaped version of it, allocated
389 * from RESULT_POOL. Otherwise, return PATH directly. */
391 auto_escape_path(const char *path,
392 apr_pool_t *result_pool)
394 apr_size_t len = strlen(path);
396 const char esc = '\x1b';
398 for (i = 0; i < len; ++i)
401 svn_stringbuf_t *escaped = svn_stringbuf_create_ensure(2 * len,
403 for (i = 0; i < len; ++i)
406 svn_stringbuf_appendbyte(escaped, esc);
407 svn_stringbuf_appendbyte(escaped, path[i] + 'A' - 1);
411 svn_stringbuf_appendbyte(escaped, path[i]);
414 return escaped->data;
420 /* If PATH has been escaped, return the un-escaped version of it, allocated
421 * from RESULT_POOL. Otherwise, return PATH directly. */
423 auto_unescape_path(const char *path,
424 apr_pool_t *result_pool)
426 const char esc = '\x1b';
427 if (strchr(path, esc))
429 apr_size_t len = strlen(path);
432 svn_stringbuf_t *unescaped = svn_stringbuf_create_ensure(len,
434 for (i = 0; i < len; ++i)
436 svn_stringbuf_appendbyte(unescaped, path[++i] + 1 - 'A');
438 svn_stringbuf_appendbyte(unescaped, path[i]);
440 return unescaped->data;
446 /* Find entry HEADER_NAME in HEADERS and parse its value into *ID. */
448 read_id_part(svn_fs_x__id_t *id,
450 const char *header_name)
452 const char *value = svn_hash_gets(headers, header_name);
454 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
455 _("Missing %s field in node-rev"),
458 SVN_ERR(svn_fs_x__id_parse(id, value));
463 svn_fs_x__read_noderev(svn_fs_x__noderev_t **noderev_p,
464 svn_stream_t *stream,
465 apr_pool_t *result_pool,
466 apr_pool_t *scratch_pool)
469 svn_fs_x__noderev_t *noderev;
471 const char *noderev_id;
473 SVN_ERR(read_header_block(&headers, stream, scratch_pool));
474 SVN_ERR(svn_stream_close(stream));
476 noderev = apr_pcalloc(result_pool, sizeof(*noderev));
478 /* for error messages later */
479 noderev_id = svn_hash_gets(headers, HEADER_ID);
481 /* Read the node-rev id. */
482 SVN_ERR(read_id_part(&noderev->noderev_id, headers, HEADER_ID));
483 SVN_ERR(read_id_part(&noderev->node_id, headers, HEADER_NODE));
484 SVN_ERR(read_id_part(&noderev->copy_id, headers, HEADER_COPY));
487 value = svn_hash_gets(headers, HEADER_TYPE);
489 if ((value == NULL) ||
490 ( strcmp(value, SVN_FS_X__KIND_FILE)
491 && strcmp(value, SVN_FS_X__KIND_DIR)))
492 /* ### s/kind/type/ */
493 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
494 _("Missing kind field in node-rev '%s'"),
497 noderev->kind = (strcmp(value, SVN_FS_X__KIND_FILE) == 0)
501 /* Read the 'count' field. */
502 value = svn_hash_gets(headers, HEADER_COUNT);
504 SVN_ERR(svn_cstring_atoi(&noderev->predecessor_count, value));
506 noderev->predecessor_count = 0;
508 /* Get the properties location. */
509 value = svn_hash_gets(headers, HEADER_PROPS);
512 SVN_ERR(read_rep_offsets(&noderev->prop_rep, value,
513 &noderev->noderev_id, result_pool,
517 /* Get the data location. */
518 value = svn_hash_gets(headers, HEADER_TEXT);
521 SVN_ERR(read_rep_offsets(&noderev->data_rep, value,
522 &noderev->noderev_id, result_pool,
526 /* Get the created path. */
527 value = svn_hash_gets(headers, HEADER_CPATH);
530 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
531 _("Missing cpath field in node-rev '%s'"),
536 if (!svn_fspath__is_canonical(value))
537 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
538 _("Non-canonical cpath field in node-rev '%s'"),
541 noderev->created_path = auto_unescape_path(apr_pstrdup(result_pool,
546 /* Get the predecessor ID. */
547 value = svn_hash_gets(headers, HEADER_PRED);
549 SVN_ERR(svn_fs_x__id_parse(&noderev->predecessor_id, value));
551 svn_fs_x__id_reset(&noderev->predecessor_id);
553 /* Get the copyroot. */
554 value = svn_hash_gets(headers, HEADER_COPYROOT);
557 noderev->copyroot_path = noderev->created_path;
558 noderev->copyroot_rev
559 = svn_fs_x__get_revnum(noderev->noderev_id.change_set);
563 SVN_ERR(parse_revnum(&noderev->copyroot_rev, (const char **)&value));
565 if (!svn_fspath__is_canonical(value))
566 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
567 _("Malformed copyroot line in node-rev '%s'"),
569 noderev->copyroot_path = auto_unescape_path(apr_pstrdup(result_pool,
574 /* Get the copyfrom. */
575 value = svn_hash_gets(headers, HEADER_COPYFROM);
578 noderev->copyfrom_path = NULL;
579 noderev->copyfrom_rev = SVN_INVALID_REVNUM;
583 SVN_ERR(parse_revnum(&noderev->copyfrom_rev, (const char **)&value));
586 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
587 _("Malformed copyfrom line in node-rev '%s'"),
589 noderev->copyfrom_path = auto_unescape_path(apr_pstrdup(result_pool,
594 /* Get the mergeinfo count. */
595 value = svn_hash_gets(headers, HEADER_MINFO_CNT);
597 SVN_ERR(svn_cstring_atoi64(&noderev->mergeinfo_count, value));
599 noderev->mergeinfo_count = 0;
601 /* Get whether *this* node has mergeinfo. */
602 value = svn_hash_gets(headers, HEADER_MINFO_HERE);
603 noderev->has_mergeinfo = (value != NULL);
605 *noderev_p = noderev;
610 /* Return a textual representation of the DIGEST of given KIND.
611 * If IS_NULL is TRUE, no digest is available.
612 * Allocate the result in RESULT_POOL.
615 format_digest(const unsigned char *digest,
616 svn_checksum_kind_t kind,
617 svn_boolean_t is_null,
618 apr_pool_t *result_pool)
620 svn_checksum_t checksum;
621 checksum.digest = digest;
622 checksum.kind = kind;
627 return svn_checksum_to_cstring_display(&checksum, result_pool);
631 svn_fs_x__unparse_representation(svn_fs_x__representation_t *rep,
632 svn_boolean_t mutable_rep_truncated,
633 apr_pool_t *result_pool,
634 apr_pool_t *scratch_pool)
637 return svn_stringbuf_createf
639 "%" APR_INT64_T_FMT " %" APR_UINT64_T_FMT " %" SVN_FILESIZE_T_FMT
640 " %" SVN_FILESIZE_T_FMT " %s",
641 rep->id.change_set, rep->id.number, rep->size,
643 format_digest(rep->md5_digest, svn_checksum_md5, FALSE,
646 return svn_stringbuf_createf
648 "%" APR_INT64_T_FMT " %" APR_UINT64_T_FMT " %" SVN_FILESIZE_T_FMT
649 " %" SVN_FILESIZE_T_FMT " %s %s",
650 rep->id.change_set, rep->id.number, rep->size,
652 format_digest(rep->md5_digest, svn_checksum_md5,
653 FALSE, scratch_pool),
654 format_digest(rep->sha1_digest, svn_checksum_sha1,
655 !rep->has_sha1, scratch_pool));
660 svn_fs_x__write_noderev(svn_stream_t *outfile,
661 svn_fs_x__noderev_t *noderev,
662 apr_pool_t *scratch_pool)
664 svn_string_t *str_id;
666 str_id = svn_fs_x__id_unparse(&noderev->noderev_id, scratch_pool);
667 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_ID ": %s\n",
669 str_id = svn_fs_x__id_unparse(&noderev->node_id, scratch_pool);
670 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_NODE ": %s\n",
672 str_id = svn_fs_x__id_unparse(&noderev->copy_id, scratch_pool);
673 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COPY ": %s\n",
676 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_TYPE ": %s\n",
677 (noderev->kind == svn_node_file) ?
678 SVN_FS_X__KIND_FILE : SVN_FS_X__KIND_DIR));
680 if (svn_fs_x__id_used(&noderev->predecessor_id))
681 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_PRED ": %s\n",
682 svn_fs_x__id_unparse(&noderev->predecessor_id,
683 scratch_pool)->data));
685 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COUNT ": %d\n",
686 noderev->predecessor_count));
688 if (noderev->data_rep)
689 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_TEXT ": %s\n",
690 svn_fs_x__unparse_representation
692 noderev->kind == svn_node_dir,
693 scratch_pool, scratch_pool)->data));
695 if (noderev->prop_rep)
696 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_PROPS ": %s\n",
697 svn_fs_x__unparse_representation
699 TRUE, scratch_pool, scratch_pool)->data));
701 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_CPATH ": %s\n",
702 auto_escape_path(noderev->created_path,
705 if (noderev->copyfrom_path)
706 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COPYFROM ": %ld"
708 noderev->copyfrom_rev,
709 auto_escape_path(noderev->copyfrom_path,
712 if ( ( noderev->copyroot_rev
713 != svn_fs_x__get_revnum(noderev->noderev_id.change_set))
714 || (strcmp(noderev->copyroot_path, noderev->created_path) != 0))
715 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COPYROOT ": %ld"
717 noderev->copyroot_rev,
718 auto_escape_path(noderev->copyroot_path,
721 if (noderev->mergeinfo_count > 0)
722 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_MINFO_CNT ": %"
723 APR_INT64_T_FMT "\n",
724 noderev->mergeinfo_count));
726 if (noderev->has_mergeinfo)
727 SVN_ERR(svn_stream_puts(outfile, HEADER_MINFO_HERE ": y\n"));
729 return svn_stream_puts(outfile, "\n");
733 svn_fs_x__read_rep_header(svn_fs_x__rep_header_t **header,
734 svn_stream_t *stream,
735 apr_pool_t *result_pool,
736 apr_pool_t *scratch_pool)
738 svn_stringbuf_t *buffer;
739 char *str, *last_str;
741 svn_boolean_t eol = FALSE;
743 SVN_ERR(svn_stream_readline(stream, &buffer, "\n", &eol, scratch_pool));
745 *header = apr_pcalloc(result_pool, sizeof(**header));
746 (*header)->header_size = buffer->len + 1;
747 if (strcmp(buffer->data, REP_DELTA) == 0)
749 /* This is a delta against the empty stream. */
750 (*header)->type = svn_fs_x__rep_self_delta;
754 (*header)->type = svn_fs_x__rep_delta;
756 /* We have hopefully a DELTA vs. a non-empty base revision. */
757 last_str = buffer->data;
758 str = svn_cstring_tokenize(" ", &last_str);
759 if (! str || (strcmp(str, REP_DELTA) != 0))
762 SVN_ERR(parse_revnum(&(*header)->base_revision, (const char **)&last_str));
764 str = svn_cstring_tokenize(" ", &last_str);
767 SVN_ERR(svn_cstring_atoi64(&val, str));
768 (*header)->base_item_index = (apr_off_t)val;
770 str = svn_cstring_tokenize(" ", &last_str);
773 SVN_ERR(svn_cstring_atoi64(&val, str));
774 (*header)->base_length = (svn_filesize_t)val;
779 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
780 _("Malformed representation header"));
784 svn_fs_x__write_rep_header(svn_fs_x__rep_header_t *header,
785 svn_stream_t *stream,
786 apr_pool_t *scratch_pool)
790 switch (header->type)
792 case svn_fs_x__rep_self_delta:
793 text = REP_DELTA "\n";
797 text = apr_psprintf(scratch_pool, REP_DELTA " %ld %" APR_OFF_T_FMT
798 " %" SVN_FILESIZE_T_FMT "\n",
799 header->base_revision, header->base_item_index,
800 header->base_length);
803 return svn_error_trace(svn_stream_puts(stream, text));
806 /* Read the next entry in the changes record from file FILE and store
807 the resulting change in *CHANGE_P. If there is no next record,
808 store NULL there. Perform all allocations from POOL. */
810 read_change(svn_fs_x__change_t **change_p,
811 svn_stream_t *stream,
812 apr_pool_t *result_pool,
813 apr_pool_t *scratch_pool)
815 svn_stringbuf_t *line;
816 svn_boolean_t eof = TRUE;
817 svn_fs_x__change_t *change;
818 char *str, *last_str, *kind_str;
820 /* Default return value. */
823 SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, scratch_pool));
825 /* Check for a blank line. */
826 if (eof || (line->len == 0))
829 change = apr_pcalloc(result_pool, sizeof(*change));
830 last_str = line->data;
832 /* Get the change type. */
833 str = svn_cstring_tokenize(" ", &last_str);
835 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
836 _("Invalid changes line in rev-file"));
838 /* Don't bother to check the format number before looking for
839 * node-kinds: just read them if you find them. */
840 change->node_kind = svn_node_unknown;
841 kind_str = strchr(str, '-');
844 /* Cap off the end of "str" (the action). */
847 if (strcmp(kind_str, SVN_FS_X__KIND_FILE) == 0)
848 change->node_kind = svn_node_file;
849 else if (strcmp(kind_str, SVN_FS_X__KIND_DIR) == 0)
850 change->node_kind = svn_node_dir;
852 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
853 _("Invalid changes line in rev-file"));
856 if (strcmp(str, ACTION_MODIFY) == 0)
858 change->change_kind = svn_fs_path_change_modify;
860 else if (strcmp(str, ACTION_ADD) == 0)
862 change->change_kind = svn_fs_path_change_add;
864 else if (strcmp(str, ACTION_DELETE) == 0)
866 change->change_kind = svn_fs_path_change_delete;
868 else if (strcmp(str, ACTION_REPLACE) == 0)
870 change->change_kind = svn_fs_path_change_replace;
874 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
875 _("Invalid change kind in rev file"));
878 /* Get the text-mod flag. */
879 str = svn_cstring_tokenize(" ", &last_str);
881 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
882 _("Invalid changes line in rev-file"));
884 if (strcmp(str, FLAG_TRUE) == 0)
886 change->text_mod = TRUE;
888 else if (strcmp(str, FLAG_FALSE) == 0)
890 change->text_mod = FALSE;
894 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
895 _("Invalid text-mod flag in rev-file"));
898 /* Get the prop-mod flag. */
899 str = svn_cstring_tokenize(" ", &last_str);
901 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
902 _("Invalid changes line in rev-file"));
904 if (strcmp(str, FLAG_TRUE) == 0)
906 change->prop_mod = TRUE;
908 else if (strcmp(str, FLAG_FALSE) == 0)
910 change->prop_mod = FALSE;
914 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
915 _("Invalid prop-mod flag in rev-file"));
918 /* Get the mergeinfo-mod flag. */
919 str = svn_cstring_tokenize(" ", &last_str);
921 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
922 _("Invalid changes line in rev-file"));
924 if (strcmp(str, FLAG_TRUE) == 0)
926 change->mergeinfo_mod = svn_tristate_true;
928 else if (strcmp(str, FLAG_FALSE) == 0)
930 change->mergeinfo_mod = svn_tristate_false;
934 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
935 _("Invalid mergeinfo-mod flag in rev-file"));
938 /* Get the changed path. */
939 if (!svn_fspath__is_canonical(last_str))
940 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
941 _("Invalid path in changes line"));
943 change->path.data = auto_unescape_path(apr_pstrmemdup(result_pool,
947 change->path.len = strlen(change->path.data);
949 /* Read the next line, the copyfrom line. */
950 SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, result_pool));
951 change->copyfrom_known = TRUE;
952 if (eof || line->len == 0)
954 change->copyfrom_rev = SVN_INVALID_REVNUM;
955 change->copyfrom_path = NULL;
959 last_str = line->data;
960 SVN_ERR(parse_revnum(&change->copyfrom_rev, (const char **)&last_str));
962 if (!svn_fspath__is_canonical(last_str))
963 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
964 _("Invalid copy-from path in changes line"));
966 change->copyfrom_path = auto_unescape_path(last_str, result_pool);
975 svn_fs_x__read_changes(apr_array_header_t **changes,
976 svn_stream_t *stream,
978 apr_pool_t *result_pool,
979 apr_pool_t *scratch_pool)
981 apr_pool_t *iterpool;
983 /* Pre-allocate enough room for most change lists.
984 (will be auto-expanded as necessary).
986 Chose the default to just below 2^N such that the doubling reallocs
987 will request roughly 2^M bytes from the OS without exceeding the
988 respective two-power by just a few bytes (leaves room array and APR
989 node overhead for large enough M).
991 *changes = apr_array_make(result_pool, 63, sizeof(svn_fs_x__change_t *));
993 iterpool = svn_pool_create(scratch_pool);
994 for (; max_count > 0; --max_count)
996 svn_fs_x__change_t *change;
997 svn_pool_clear(iterpool);
998 SVN_ERR(read_change(&change, stream, result_pool, iterpool));
1002 APR_ARRAY_PUSH(*changes, svn_fs_x__change_t*) = change;
1004 svn_pool_destroy(iterpool);
1006 return SVN_NO_ERROR;
1010 svn_fs_x__read_changes_incrementally(svn_stream_t *stream,
1011 svn_fs_x__change_receiver_t
1013 void *change_receiver_baton,
1014 apr_pool_t *scratch_pool)
1016 svn_fs_x__change_t *change;
1017 apr_pool_t *iterpool;
1019 iterpool = svn_pool_create(scratch_pool);
1022 svn_pool_clear(iterpool);
1024 SVN_ERR(read_change(&change, stream, iterpool, iterpool));
1026 SVN_ERR(change_receiver(change_receiver_baton, change, iterpool));
1029 svn_pool_destroy(iterpool);
1031 return SVN_NO_ERROR;
1034 /* Write a single change entry, path PATH, change CHANGE, to STREAM.
1036 All temporary allocations are in SCRATCH_POOL. */
1037 static svn_error_t *
1038 write_change_entry(svn_stream_t *stream,
1039 svn_fs_x__change_t *change,
1040 apr_pool_t *scratch_pool)
1042 const char *change_string = NULL;
1043 const char *kind_string = "";
1044 svn_stringbuf_t *buf;
1047 switch (change->change_kind)
1049 case svn_fs_path_change_modify:
1050 change_string = ACTION_MODIFY;
1052 case svn_fs_path_change_add:
1053 change_string = ACTION_ADD;
1055 case svn_fs_path_change_delete:
1056 change_string = ACTION_DELETE;
1058 case svn_fs_path_change_replace:
1059 change_string = ACTION_REPLACE;
1062 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
1063 _("Invalid change type %d"),
1064 change->change_kind);
1067 SVN_ERR_ASSERT(change->node_kind == svn_node_dir
1068 || change->node_kind == svn_node_file);
1069 kind_string = apr_psprintf(scratch_pool, "-%s",
1070 change->node_kind == svn_node_dir
1071 ? SVN_FS_X__KIND_DIR
1072 : SVN_FS_X__KIND_FILE);
1074 buf = svn_stringbuf_createf(scratch_pool, "%s%s %s %s %s %s\n",
1075 change_string, kind_string,
1076 change->text_mod ? FLAG_TRUE : FLAG_FALSE,
1077 change->prop_mod ? FLAG_TRUE : FLAG_FALSE,
1078 change->mergeinfo_mod == svn_tristate_true
1079 ? FLAG_TRUE : FLAG_FALSE,
1080 auto_escape_path(change->path.data, scratch_pool));
1082 if (SVN_IS_VALID_REVNUM(change->copyfrom_rev))
1084 svn_stringbuf_appendcstr(buf, apr_psprintf(scratch_pool, "%ld %s",
1085 change->copyfrom_rev,
1086 auto_escape_path(change->copyfrom_path,
1090 svn_stringbuf_appendbyte(buf, '\n');
1092 /* Write all change info in one write call. */
1094 return svn_error_trace(svn_stream_write(stream, buf->data, &len));
1098 svn_fs_x__write_changes(svn_stream_t *stream,
1100 apr_hash_t *changes,
1101 svn_boolean_t terminate_list,
1102 apr_pool_t *scratch_pool)
1104 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1105 apr_array_header_t *sorted_changed_paths;
1108 /* For the sake of the repository administrator sort the changes so
1109 that the final file is deterministic and repeatable, however the
1110 rest of the FSX code doesn't require any particular order here.
1112 Also, this sorting is only effective in writing all entries with
1113 a single call as write_final_changed_path_info() does. For the
1114 list being written incrementally during transaction, we actually
1115 *must not* change the order of entries from different calls.
1117 sorted_changed_paths = svn_sort__hash(changes,
1118 svn_sort_compare_items_lexically,
1121 /* Write all items to disk in the new order. */
1122 for (i = 0; i < sorted_changed_paths->nelts; ++i)
1124 svn_fs_x__change_t *change;
1126 svn_pool_clear(iterpool);
1127 change = APR_ARRAY_IDX(sorted_changed_paths, i, svn_sort__item_t).value;
1129 /* Write out the new entry into the final rev-file. */
1130 SVN_ERR(write_change_entry(stream, change, iterpool));
1134 svn_stream_puts(stream, "\n");
1136 svn_pool_destroy(iterpool);
1138 return SVN_NO_ERROR;
1142 svn_fs_x__parse_properties(apr_hash_t **properties,
1143 const svn_string_t *content,
1144 apr_pool_t *result_pool)
1146 const apr_byte_t *p = (const apr_byte_t *)content->data;
1147 const apr_byte_t *end = p + content->len;
1150 *properties = apr_hash_make(result_pool);
1152 /* Extract the number of properties we are expected to read. */
1153 p = svn__decode_uint(&count, p, end);
1155 /* Read all the properties we find.
1156 Because prop-name and prop-value are nicely NUL-terminated
1157 sub-strings of CONTENT, we can simply reference them there.
1158 I.e. there is no need to copy them around.
1162 apr_uint64_t value_len;
1163 svn_string_t *value;
1165 const char *key = (const char *)p;
1167 /* Note that this may never overflow / segfault because
1168 CONTENT itself is NUL-terminated. */
1169 apr_size_t key_len = strlen(key);
1172 return svn_error_createf(SVN_ERR_FS_CORRUPT_PROPLIST, NULL,
1173 "Property name not NUL terminated");
1176 return svn_error_createf(SVN_ERR_FS_CORRUPT_PROPLIST, NULL,
1177 "Property value missing");
1178 p = svn__decode_uint(&value_len, p, end);
1179 if (value_len >= (end - p))
1180 return svn_error_createf(SVN_ERR_FS_CORRUPT_PROPLIST, NULL,
1181 "Property value too long");
1183 value = apr_pcalloc(result_pool, sizeof(*value));
1184 value->data = (const char *)p;
1185 value->len = (apr_size_t)value_len;
1187 return svn_error_createf(SVN_ERR_FS_CORRUPT_PROPLIST, NULL,
1188 "Property value not NUL terminated");
1190 p += value->len + 1;
1192 apr_hash_set(*properties, key, key_len, value);
1195 /* Check that we read the expected number of properties. */
1196 if ((apr_uint64_t)apr_hash_count(*properties) != count)
1197 return svn_error_createf(SVN_ERR_FS_CORRUPT_PROPLIST, NULL,
1198 "Property count mismatch");
1200 return SVN_NO_ERROR;
1204 svn_fs_x__write_properties(svn_stream_t *stream,
1205 apr_hash_t *proplist,
1206 apr_pool_t *scratch_pool)
1208 apr_byte_t buffer[SVN__MAX_ENCODED_UINT_LEN];
1210 apr_hash_index_t *hi;
1212 /* Write the number of properties in this list. */
1213 len = svn__encode_uint(buffer, apr_hash_count(proplist)) - buffer;
1214 SVN_ERR(svn_stream_write(stream, (const char *)buffer, &len));
1216 /* Serialize each property as follows:
1218 <Value-len> <Prop-value> <NUL>
1220 for (hi = apr_hash_first(scratch_pool, proplist);
1222 hi = apr_hash_next(hi))
1226 svn_string_t *value;
1227 apr_hash_this(hi, (const void **)&key, (apr_ssize_t *)&key_len,
1230 /* Include the terminating NUL. */
1232 SVN_ERR(svn_stream_write(stream, key, &key_len));
1234 len = svn__encode_uint(buffer, value->len) - buffer;
1235 SVN_ERR(svn_stream_write(stream, (const char *)buffer, &len));
1236 SVN_ERR(svn_stream_write(stream, value->data, &value->len));
1238 /* Terminate with NUL. */
1240 SVN_ERR(svn_stream_write(stream, "", &len));
1243 return SVN_NO_ERROR;