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