1 /* low_level.c --- low level r/w access to fs_x 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"
59 #define ACTION_RESET "reset"
61 /* True and False flags. */
62 #define FLAG_TRUE "true"
63 #define FLAG_FALSE "false"
65 /* Kinds of representation. */
66 #define REP_DELTA "DELTA"
68 /* An arbitrary maximum path length, so clients can't run us out of memory
69 * by giving us arbitrarily large paths. */
70 #define FSX_MAX_PATH_LEN 4096
72 /* The 256 is an arbitrary size large enough to hold the node id and the
74 #define MAX_CHANGE_LINE_LEN FSX_MAX_PATH_LEN + 256
76 /* Convert the C string in *TEXT to a revision number and return it in *REV.
77 * Overflows, negative values other than -1 and terminating characters other
78 * than 0x20 or 0x0 will cause an error. Set *TEXT to the first char after
79 * the initial separator or to EOS.
82 parse_revnum(svn_revnum_t *rev,
85 const char *string = *text;
86 if ((string[0] == '-') && (string[1] == '1'))
88 *rev = SVN_INVALID_REVNUM;
93 SVN_ERR(svn_revnum_parse(rev, string, &string));
98 else if (*string != '\0')
99 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
100 _("Invalid character in revision number"));
107 svn_fs_x__parse_footer(apr_off_t *l2p_offset,
108 svn_checksum_t **l2p_checksum,
109 apr_off_t *p2l_offset,
110 svn_checksum_t **p2l_checksum,
111 svn_stringbuf_t *footer,
113 apr_pool_t *result_pool)
116 char *last_str = footer->data;
118 /* Get the L2P offset. */
119 const char *str = svn_cstring_tokenize(" ", &last_str);
121 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
122 _("Invalid revision footer"));
124 SVN_ERR(svn_cstring_atoi64(&val, str));
125 *l2p_offset = (apr_off_t)val;
127 /* Get the L2P checksum. */
128 str = svn_cstring_tokenize(" ", &last_str);
130 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
131 _("Invalid revision footer"));
133 SVN_ERR(svn_checksum_parse_hex(l2p_checksum, svn_checksum_md5, str,
136 /* Get the P2L offset. */
137 str = svn_cstring_tokenize(" ", &last_str);
139 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
140 _("Invalid revision footer"));
142 SVN_ERR(svn_cstring_atoi64(&val, str));
143 *p2l_offset = (apr_off_t)val;
145 /* Get the P2L checksum. */
146 str = svn_cstring_tokenize(" ", &last_str);
148 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
149 _("Invalid revision footer"));
151 SVN_ERR(svn_checksum_parse_hex(p2l_checksum, svn_checksum_md5, str,
158 svn_fs_x__unparse_footer(apr_off_t l2p_offset,
159 svn_checksum_t *l2p_checksum,
160 apr_off_t p2l_offset,
161 svn_checksum_t *p2l_checksum,
162 apr_pool_t *result_pool,
163 apr_pool_t *scratch_pool)
165 return svn_stringbuf_createf(result_pool,
166 "%" APR_OFF_T_FMT " %s %" APR_OFF_T_FMT " %s",
168 svn_checksum_to_cstring(l2p_checksum,
171 svn_checksum_to_cstring(p2l_checksum,
175 /* Given a revision file FILE that has been pre-positioned at the
176 beginning of a Node-Rev header block, read in that header block and
177 store it in the apr_hash_t HEADERS. All allocations will be from
180 read_header_block(apr_hash_t **headers,
181 svn_stream_t *stream,
182 apr_pool_t *result_pool)
184 *headers = svn_hash__make(result_pool);
188 svn_stringbuf_t *header_str;
189 const char *name, *value;
194 SVN_ERR(svn_stream_readline(stream, &header_str, "\n", &eof,
197 if (eof || header_str->len == 0)
198 break; /* end of header block */
200 while (header_str->data[i] != ':')
202 if (header_str->data[i] == '\0')
203 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
204 _("Found malformed header '%s' in "
210 /* Create a 'name' string and point to it. */
211 header_str->data[i] = '\0';
212 name = header_str->data;
215 /* Check if we have enough data to parse. */
216 if (i + 2 > header_str->len)
218 /* Restore the original line for the error. */
219 header_str->data[i] = ':';
220 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
221 _("Found malformed header '%s' in "
226 /* Skip over the NULL byte and the space following it. */
229 value = header_str->data + i;
231 /* header_str is safely in our pool, so we can use bits of it as
233 apr_hash_set(*headers, name, name_len, value);
240 svn_fs_x__parse_representation(svn_fs_x__representation_t **rep_p,
241 svn_stringbuf_t *text,
242 apr_pool_t *result_pool,
243 apr_pool_t *scratch_pool)
245 svn_fs_x__representation_t *rep;
248 char *string = text->data;
249 svn_checksum_t *checksum;
251 rep = apr_pcalloc(result_pool, sizeof(*rep));
254 str = svn_cstring_tokenize(" ", &string);
256 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
257 _("Malformed text representation offset line in node-rev"));
259 SVN_ERR(svn_cstring_atoi64(&rep->id.change_set, str));
261 /* while in transactions, it is legal to simply write "-1" */
262 if (rep->id.change_set == -1)
265 str = svn_cstring_tokenize(" ", &string);
268 if (rep->id.change_set == SVN_FS_X__INVALID_CHANGE_SET)
271 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
272 _("Malformed text representation offset line in node-rev"));
275 SVN_ERR(svn_cstring_atoi64(&val, str));
276 rep->id.number = (apr_off_t)val;
278 str = svn_cstring_tokenize(" ", &string);
280 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
281 _("Malformed text representation offset line in node-rev"));
283 SVN_ERR(svn_cstring_atoi64(&val, str));
284 rep->size = (svn_filesize_t)val;
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(&val, str));
292 rep->expanded_size = (svn_filesize_t)val;
294 /* Read in the MD5 hash. */
295 str = svn_cstring_tokenize(" ", &string);
296 if ((str == NULL) || (strlen(str) != (APR_MD5_DIGESTSIZE * 2)))
297 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
298 _("Malformed text representation offset line in node-rev"));
300 SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_md5, str,
303 memcpy(rep->md5_digest, checksum->digest, sizeof(rep->md5_digest));
305 /* The remaining fields are only used for formats >= 4, so check that. */
306 str = svn_cstring_tokenize(" ", &string);
310 /* Read the SHA1 hash. */
311 if (strlen(str) != (APR_SHA1_DIGESTSIZE * 2))
312 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
313 _("Malformed text representation offset line in node-rev"));
315 SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_sha1, str,
317 rep->has_sha1 = checksum != NULL;
319 memcpy(rep->sha1_digest, checksum->digest, sizeof(rep->sha1_digest));
324 /* Wrap read_rep_offsets_body(), extracting its TXN_ID from our NODEREV_ID,
325 and adding an error message. */
327 read_rep_offsets(svn_fs_x__representation_t **rep_p,
329 const svn_fs_x__id_t *noderev_id,
330 apr_pool_t *result_pool,
331 apr_pool_t *scratch_pool)
334 = svn_fs_x__parse_representation(rep_p,
335 svn_stringbuf_create_wrap(string,
341 const svn_string_t *id_unparsed;
344 id_unparsed = svn_fs_x__id_unparse(noderev_id, scratch_pool);
345 where = apr_psprintf(scratch_pool,
346 _("While reading representation offsets "
347 "for node-revision '%s':"),
350 return svn_error_quick_wrap(err, where);
356 /* If PATH needs to be escaped, return an escaped version of it, allocated
357 * from RESULT_POOL. Otherwise, return PATH directly. */
359 auto_escape_path(const char *path,
360 apr_pool_t *result_pool)
362 apr_size_t len = strlen(path);
364 const char esc = '\x1b';
366 for (i = 0; i < len; ++i)
369 svn_stringbuf_t *escaped = svn_stringbuf_create_ensure(2 * len,
371 for (i = 0; i < len; ++i)
374 svn_stringbuf_appendbyte(escaped, esc);
375 svn_stringbuf_appendbyte(escaped, path[i] + 'A' - 1);
379 svn_stringbuf_appendbyte(escaped, path[i]);
382 return escaped->data;
388 /* If PATH has been escaped, return the un-escaped version of it, allocated
389 * from RESULT_POOL. Otherwise, return PATH directly. */
391 auto_unescape_path(const char *path,
392 apr_pool_t *result_pool)
394 const char esc = '\x1b';
395 if (strchr(path, esc))
397 apr_size_t len = strlen(path);
400 svn_stringbuf_t *unescaped = svn_stringbuf_create_ensure(len,
402 for (i = 0; i < len; ++i)
404 svn_stringbuf_appendbyte(unescaped, path[++i] + 1 - 'A');
406 svn_stringbuf_appendbyte(unescaped, path[i]);
408 return unescaped->data;
414 /* Find entry HEADER_NAME in HEADERS and parse its value into *ID. */
416 read_id_part(svn_fs_x__id_t *id,
418 const char *header_name)
420 const char *value = svn_hash_gets(headers, header_name);
422 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
423 _("Missing %s field in node-rev"),
426 SVN_ERR(svn_fs_x__id_parse(id, value));
431 svn_fs_x__read_noderev(svn_fs_x__noderev_t **noderev_p,
432 svn_stream_t *stream,
433 apr_pool_t *result_pool,
434 apr_pool_t *scratch_pool)
437 svn_fs_x__noderev_t *noderev;
439 const char *noderev_id;
441 SVN_ERR(read_header_block(&headers, stream, scratch_pool));
442 SVN_ERR(svn_stream_close(stream));
444 noderev = apr_pcalloc(result_pool, sizeof(*noderev));
446 /* for error messages later */
447 noderev_id = svn_hash_gets(headers, HEADER_ID);
449 /* Read the node-rev id. */
450 SVN_ERR(read_id_part(&noderev->noderev_id, headers, HEADER_ID));
451 SVN_ERR(read_id_part(&noderev->node_id, headers, HEADER_NODE));
452 SVN_ERR(read_id_part(&noderev->copy_id, headers, HEADER_COPY));
455 value = svn_hash_gets(headers, HEADER_TYPE);
457 if ((value == NULL) ||
458 ( strcmp(value, SVN_FS_X__KIND_FILE)
459 && strcmp(value, SVN_FS_X__KIND_DIR)))
460 /* ### s/kind/type/ */
461 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
462 _("Missing kind field in node-rev '%s'"),
465 noderev->kind = (strcmp(value, SVN_FS_X__KIND_FILE) == 0)
469 /* Read the 'count' field. */
470 value = svn_hash_gets(headers, HEADER_COUNT);
472 SVN_ERR(svn_cstring_atoi(&noderev->predecessor_count, value));
474 noderev->predecessor_count = 0;
476 /* Get the properties location. */
477 value = svn_hash_gets(headers, HEADER_PROPS);
480 SVN_ERR(read_rep_offsets(&noderev->prop_rep, value,
481 &noderev->noderev_id, result_pool,
485 /* Get the data location. */
486 value = svn_hash_gets(headers, HEADER_TEXT);
489 SVN_ERR(read_rep_offsets(&noderev->data_rep, value,
490 &noderev->noderev_id, result_pool,
494 /* Get the created path. */
495 value = svn_hash_gets(headers, HEADER_CPATH);
498 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
499 _("Missing cpath field in node-rev '%s'"),
504 if (!svn_fspath__is_canonical(value))
505 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
506 _("Non-canonical cpath field in node-rev '%s'"),
509 noderev->created_path = auto_unescape_path(apr_pstrdup(result_pool,
514 /* Get the predecessor ID. */
515 value = svn_hash_gets(headers, HEADER_PRED);
517 SVN_ERR(svn_fs_x__id_parse(&noderev->predecessor_id, value));
519 svn_fs_x__id_reset(&noderev->predecessor_id);
521 /* Get the copyroot. */
522 value = svn_hash_gets(headers, HEADER_COPYROOT);
525 noderev->copyroot_path = noderev->created_path;
526 noderev->copyroot_rev
527 = svn_fs_x__get_revnum(noderev->noderev_id.change_set);
531 SVN_ERR(parse_revnum(&noderev->copyroot_rev, (const char **)&value));
533 if (!svn_fspath__is_canonical(value))
534 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
535 _("Malformed copyroot line in node-rev '%s'"),
537 noderev->copyroot_path = auto_unescape_path(apr_pstrdup(result_pool,
542 /* Get the copyfrom. */
543 value = svn_hash_gets(headers, HEADER_COPYFROM);
546 noderev->copyfrom_path = NULL;
547 noderev->copyfrom_rev = SVN_INVALID_REVNUM;
551 SVN_ERR(parse_revnum(&noderev->copyfrom_rev, (const char **)&value));
554 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
555 _("Malformed copyfrom line in node-rev '%s'"),
557 noderev->copyfrom_path = auto_unescape_path(apr_pstrdup(result_pool,
562 /* Get the mergeinfo count. */
563 value = svn_hash_gets(headers, HEADER_MINFO_CNT);
565 SVN_ERR(svn_cstring_atoi64(&noderev->mergeinfo_count, value));
567 noderev->mergeinfo_count = 0;
569 /* Get whether *this* node has mergeinfo. */
570 value = svn_hash_gets(headers, HEADER_MINFO_HERE);
571 noderev->has_mergeinfo = (value != NULL);
573 *noderev_p = noderev;
578 /* Return a textual representation of the DIGEST of given KIND.
579 * If IS_NULL is TRUE, no digest is available.
580 * Allocate the result in RESULT_POOL.
583 format_digest(const unsigned char *digest,
584 svn_checksum_kind_t kind,
585 svn_boolean_t is_null,
586 apr_pool_t *result_pool)
588 svn_checksum_t checksum;
589 checksum.digest = digest;
590 checksum.kind = kind;
595 return svn_checksum_to_cstring_display(&checksum, result_pool);
599 svn_fs_x__unparse_representation(svn_fs_x__representation_t *rep,
600 svn_boolean_t mutable_rep_truncated,
601 apr_pool_t *result_pool,
602 apr_pool_t *scratch_pool)
605 return svn_stringbuf_createf
607 "%" APR_INT64_T_FMT " %" APR_UINT64_T_FMT " %" SVN_FILESIZE_T_FMT
608 " %" SVN_FILESIZE_T_FMT " %s",
609 rep->id.change_set, rep->id.number, rep->size,
611 format_digest(rep->md5_digest, svn_checksum_md5, FALSE,
614 return svn_stringbuf_createf
616 "%" APR_INT64_T_FMT " %" APR_UINT64_T_FMT " %" SVN_FILESIZE_T_FMT
617 " %" SVN_FILESIZE_T_FMT " %s %s",
618 rep->id.change_set, rep->id.number, rep->size,
620 format_digest(rep->md5_digest, svn_checksum_md5,
621 FALSE, scratch_pool),
622 format_digest(rep->sha1_digest, svn_checksum_sha1,
623 !rep->has_sha1, scratch_pool));
628 svn_fs_x__write_noderev(svn_stream_t *outfile,
629 svn_fs_x__noderev_t *noderev,
630 apr_pool_t *scratch_pool)
632 svn_string_t *str_id;
634 str_id = svn_fs_x__id_unparse(&noderev->noderev_id, scratch_pool);
635 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_ID ": %s\n",
637 str_id = svn_fs_x__id_unparse(&noderev->node_id, scratch_pool);
638 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_NODE ": %s\n",
640 str_id = svn_fs_x__id_unparse(&noderev->copy_id, scratch_pool);
641 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COPY ": %s\n",
644 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_TYPE ": %s\n",
645 (noderev->kind == svn_node_file) ?
646 SVN_FS_X__KIND_FILE : SVN_FS_X__KIND_DIR));
648 if (svn_fs_x__id_used(&noderev->predecessor_id))
649 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_PRED ": %s\n",
650 svn_fs_x__id_unparse(&noderev->predecessor_id,
651 scratch_pool)->data));
653 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COUNT ": %d\n",
654 noderev->predecessor_count));
656 if (noderev->data_rep)
657 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_TEXT ": %s\n",
658 svn_fs_x__unparse_representation
660 noderev->kind == svn_node_dir,
661 scratch_pool, scratch_pool)->data));
663 if (noderev->prop_rep)
664 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_PROPS ": %s\n",
665 svn_fs_x__unparse_representation
667 TRUE, scratch_pool, scratch_pool)->data));
669 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_CPATH ": %s\n",
670 auto_escape_path(noderev->created_path,
673 if (noderev->copyfrom_path)
674 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COPYFROM ": %ld"
676 noderev->copyfrom_rev,
677 auto_escape_path(noderev->copyfrom_path,
680 if ( ( noderev->copyroot_rev
681 != svn_fs_x__get_revnum(noderev->noderev_id.change_set))
682 || (strcmp(noderev->copyroot_path, noderev->created_path) != 0))
683 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COPYROOT ": %ld"
685 noderev->copyroot_rev,
686 auto_escape_path(noderev->copyroot_path,
689 if (noderev->mergeinfo_count > 0)
690 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_MINFO_CNT ": %"
691 APR_INT64_T_FMT "\n",
692 noderev->mergeinfo_count));
694 if (noderev->has_mergeinfo)
695 SVN_ERR(svn_stream_puts(outfile, HEADER_MINFO_HERE ": y\n"));
697 return svn_stream_puts(outfile, "\n");
701 svn_fs_x__read_rep_header(svn_fs_x__rep_header_t **header,
702 svn_stream_t *stream,
703 apr_pool_t *result_pool,
704 apr_pool_t *scratch_pool)
706 svn_stringbuf_t *buffer;
707 char *str, *last_str;
709 svn_boolean_t eol = FALSE;
711 SVN_ERR(svn_stream_readline(stream, &buffer, "\n", &eol, scratch_pool));
713 *header = apr_pcalloc(result_pool, sizeof(**header));
714 (*header)->header_size = buffer->len + 1;
715 if (strcmp(buffer->data, REP_DELTA) == 0)
717 /* This is a delta against the empty stream. */
718 (*header)->type = svn_fs_x__rep_self_delta;
722 (*header)->type = svn_fs_x__rep_delta;
724 /* We have hopefully a DELTA vs. a non-empty base revision. */
725 last_str = buffer->data;
726 str = svn_cstring_tokenize(" ", &last_str);
727 if (! str || (strcmp(str, REP_DELTA) != 0))
730 SVN_ERR(parse_revnum(&(*header)->base_revision, (const char **)&last_str));
732 str = svn_cstring_tokenize(" ", &last_str);
735 SVN_ERR(svn_cstring_atoi64(&val, str));
736 (*header)->base_item_index = (apr_off_t)val;
738 str = svn_cstring_tokenize(" ", &last_str);
741 SVN_ERR(svn_cstring_atoi64(&val, str));
742 (*header)->base_length = (svn_filesize_t)val;
747 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
748 _("Malformed representation header"));
752 svn_fs_x__write_rep_header(svn_fs_x__rep_header_t *header,
753 svn_stream_t *stream,
754 apr_pool_t *scratch_pool)
758 switch (header->type)
760 case svn_fs_x__rep_self_delta:
761 text = REP_DELTA "\n";
765 text = apr_psprintf(scratch_pool, REP_DELTA " %ld %" APR_OFF_T_FMT
766 " %" SVN_FILESIZE_T_FMT "\n",
767 header->base_revision, header->base_item_index,
768 header->base_length);
771 return svn_error_trace(svn_stream_puts(stream, text));
774 /* Read the next entry in the changes record from file FILE and store
775 the resulting change in *CHANGE_P. If there is no next record,
776 store NULL there. Perform all allocations from POOL. */
778 read_change(svn_fs_x__change_t **change_p,
779 svn_stream_t *stream,
780 apr_pool_t *result_pool,
781 apr_pool_t *scratch_pool)
783 svn_stringbuf_t *line;
784 svn_boolean_t eof = TRUE;
785 svn_fs_x__change_t *change;
786 char *str, *last_str, *kind_str;
788 /* Default return value. */
791 SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, scratch_pool));
793 /* Check for a blank line. */
794 if (eof || (line->len == 0))
797 change = apr_pcalloc(result_pool, sizeof(*change));
798 last_str = line->data;
800 /* Get the node-id of the change. */
801 str = svn_cstring_tokenize(" ", &last_str);
803 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
804 _("Invalid changes line in rev-file"));
806 SVN_ERR(svn_fs_x__id_parse(&change->noderev_id, str));
808 /* Get the change type. */
809 str = svn_cstring_tokenize(" ", &last_str);
811 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
812 _("Invalid changes line in rev-file"));
814 /* Don't bother to check the format number before looking for
815 * node-kinds: just read them if you find them. */
816 change->node_kind = svn_node_unknown;
817 kind_str = strchr(str, '-');
820 /* Cap off the end of "str" (the action). */
823 if (strcmp(kind_str, SVN_FS_X__KIND_FILE) == 0)
824 change->node_kind = svn_node_file;
825 else if (strcmp(kind_str, SVN_FS_X__KIND_DIR) == 0)
826 change->node_kind = svn_node_dir;
828 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
829 _("Invalid changes line in rev-file"));
832 if (strcmp(str, ACTION_MODIFY) == 0)
834 change->change_kind = svn_fs_path_change_modify;
836 else if (strcmp(str, ACTION_ADD) == 0)
838 change->change_kind = svn_fs_path_change_add;
840 else if (strcmp(str, ACTION_DELETE) == 0)
842 change->change_kind = svn_fs_path_change_delete;
844 else if (strcmp(str, ACTION_REPLACE) == 0)
846 change->change_kind = svn_fs_path_change_replace;
848 else if (strcmp(str, ACTION_RESET) == 0)
850 change->change_kind = svn_fs_path_change_reset;
854 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
855 _("Invalid change kind in rev file"));
858 /* Get the text-mod flag. */
859 str = svn_cstring_tokenize(" ", &last_str);
861 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
862 _("Invalid changes line in rev-file"));
864 if (strcmp(str, FLAG_TRUE) == 0)
866 change->text_mod = TRUE;
868 else if (strcmp(str, FLAG_FALSE) == 0)
870 change->text_mod = FALSE;
874 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
875 _("Invalid text-mod flag in rev-file"));
878 /* Get the prop-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->prop_mod = TRUE;
888 else if (strcmp(str, FLAG_FALSE) == 0)
890 change->prop_mod = FALSE;
894 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
895 _("Invalid prop-mod flag in rev-file"));
898 /* Get the mergeinfo-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->mergeinfo_mod = svn_tristate_true;
908 else if (strcmp(str, FLAG_FALSE) == 0)
910 change->mergeinfo_mod = svn_tristate_false;
914 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
915 _("Invalid mergeinfo-mod flag in rev-file"));
918 /* Get the changed path. */
919 if (!svn_fspath__is_canonical(last_str))
920 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
921 _("Invalid path in changes line"));
923 change->path.data = auto_unescape_path(apr_pstrmemdup(result_pool,
927 change->path.len = strlen(change->path.data);
929 /* Read the next line, the copyfrom line. */
930 SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, result_pool));
931 change->copyfrom_known = TRUE;
932 if (eof || line->len == 0)
934 change->copyfrom_rev = SVN_INVALID_REVNUM;
935 change->copyfrom_path = NULL;
939 last_str = line->data;
940 SVN_ERR(parse_revnum(&change->copyfrom_rev, (const char **)&last_str));
942 if (!svn_fspath__is_canonical(last_str))
943 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
944 _("Invalid copy-from path in changes line"));
946 change->copyfrom_path = auto_unescape_path(last_str, result_pool);
955 svn_fs_x__read_changes(apr_array_header_t **changes,
956 svn_stream_t *stream,
957 apr_pool_t *result_pool,
958 apr_pool_t *scratch_pool)
960 svn_fs_x__change_t *change;
961 apr_pool_t *iterpool;
963 /* Pre-allocate enough room for most change lists.
964 (will be auto-expanded as necessary).
966 Chose the default to just below 2^N such that the doubling reallocs
967 will request roughly 2^M bytes from the OS without exceeding the
968 respective two-power by just a few bytes (leaves room array and APR
969 node overhead for large enough M).
971 *changes = apr_array_make(result_pool, 63, sizeof(svn_fs_x__change_t *));
973 SVN_ERR(read_change(&change, stream, result_pool, scratch_pool));
974 iterpool = svn_pool_create(scratch_pool);
977 APR_ARRAY_PUSH(*changes, svn_fs_x__change_t*) = change;
978 SVN_ERR(read_change(&change, stream, result_pool, iterpool));
979 svn_pool_clear(iterpool);
981 svn_pool_destroy(iterpool);
987 svn_fs_x__read_changes_incrementally(svn_stream_t *stream,
988 svn_fs_x__change_receiver_t
990 void *change_receiver_baton,
991 apr_pool_t *scratch_pool)
993 svn_fs_x__change_t *change;
994 apr_pool_t *iterpool;
996 iterpool = svn_pool_create(scratch_pool);
999 svn_pool_clear(iterpool);
1001 SVN_ERR(read_change(&change, stream, iterpool, iterpool));
1003 SVN_ERR(change_receiver(change_receiver_baton, change, iterpool));
1006 svn_pool_destroy(iterpool);
1008 return SVN_NO_ERROR;
1011 /* Write a single change entry, path PATH, change CHANGE, to STREAM.
1013 All temporary allocations are in SCRATCH_POOL. */
1014 static svn_error_t *
1015 write_change_entry(svn_stream_t *stream,
1016 svn_fs_x__change_t *change,
1017 apr_pool_t *scratch_pool)
1020 const char *change_string = NULL;
1021 const char *kind_string = "";
1022 svn_stringbuf_t *buf;
1025 switch (change->change_kind)
1027 case svn_fs_path_change_modify:
1028 change_string = ACTION_MODIFY;
1030 case svn_fs_path_change_add:
1031 change_string = ACTION_ADD;
1033 case svn_fs_path_change_delete:
1034 change_string = ACTION_DELETE;
1036 case svn_fs_path_change_replace:
1037 change_string = ACTION_REPLACE;
1039 case svn_fs_path_change_reset:
1040 change_string = ACTION_RESET;
1043 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
1044 _("Invalid change type %d"),
1045 change->change_kind);
1048 idstr = svn_fs_x__id_unparse(&change->noderev_id, scratch_pool)->data;
1050 SVN_ERR_ASSERT(change->node_kind == svn_node_dir
1051 || change->node_kind == svn_node_file);
1052 kind_string = apr_psprintf(scratch_pool, "-%s",
1053 change->node_kind == svn_node_dir
1054 ? SVN_FS_X__KIND_DIR
1055 : SVN_FS_X__KIND_FILE);
1057 buf = svn_stringbuf_createf(scratch_pool, "%s %s%s %s %s %s %s\n",
1058 idstr, change_string, kind_string,
1059 change->text_mod ? FLAG_TRUE : FLAG_FALSE,
1060 change->prop_mod ? FLAG_TRUE : FLAG_FALSE,
1061 change->mergeinfo_mod == svn_tristate_true
1062 ? FLAG_TRUE : FLAG_FALSE,
1063 auto_escape_path(change->path.data, scratch_pool));
1065 if (SVN_IS_VALID_REVNUM(change->copyfrom_rev))
1067 svn_stringbuf_appendcstr(buf, apr_psprintf(scratch_pool, "%ld %s",
1068 change->copyfrom_rev,
1069 auto_escape_path(change->copyfrom_path,
1073 svn_stringbuf_appendbyte(buf, '\n');
1075 /* Write all change info in one write call. */
1077 return svn_error_trace(svn_stream_write(stream, buf->data, &len));
1081 svn_fs_x__write_changes(svn_stream_t *stream,
1083 apr_hash_t *changes,
1084 svn_boolean_t terminate_list,
1085 apr_pool_t *scratch_pool)
1087 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1088 apr_array_header_t *sorted_changed_paths;
1091 /* For the sake of the repository administrator sort the changes so
1092 that the final file is deterministic and repeatable, however the
1093 rest of the FSX code doesn't require any particular order here.
1095 Also, this sorting is only effective in writing all entries with
1096 a single call as write_final_changed_path_info() does. For the
1097 list being written incrementally during transaction, we actually
1098 *must not* change the order of entries from different calls.
1100 sorted_changed_paths = svn_sort__hash(changes,
1101 svn_sort_compare_items_lexically,
1104 /* Write all items to disk in the new order. */
1105 for (i = 0; i < sorted_changed_paths->nelts; ++i)
1107 svn_fs_x__change_t *change;
1109 svn_pool_clear(iterpool);
1110 change = APR_ARRAY_IDX(sorted_changed_paths, i, svn_sort__item_t).value;
1112 /* Write out the new entry into the final rev-file. */
1113 SVN_ERR(write_change_entry(stream, change, iterpool));
1117 svn_stream_puts(stream, "\n");
1119 svn_pool_destroy(iterpool);
1121 return SVN_NO_ERROR;