low_level.c revision 362181
1/* low_level.c --- low level r/w access to FSX 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 60/* True and False flags. */ 61#define FLAG_TRUE "true" 62#define FLAG_FALSE "false" 63 64/* Kinds of representation. */ 65#define REP_DELTA "DELTA" 66 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 70 71/* The 256 is an arbitrary size large enough to hold the node id and the 72 * various flags. */ 73#define MAX_CHANGE_LINE_LEN FSX_MAX_PATH_LEN + 256 74 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. 79 */ 80static svn_error_t * 81parse_revnum(svn_revnum_t *rev, 82 const char **text) 83{ 84 const char *string = *text; 85 if ((string[0] == '-') && (string[1] == '1')) 86 { 87 *rev = SVN_INVALID_REVNUM; 88 string += 2; 89 } 90 else 91 { 92 SVN_ERR(svn_revnum_parse(rev, string, &string)); 93 } 94 95 if (*string == ' ') 96 ++string; 97 else if (*string != '\0') 98 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 99 _("Invalid character in revision number")); 100 101 *text = string; 102 return SVN_NO_ERROR; 103} 104 105/* If ERR is not NULL, wrap it MESSAGE. The latter must have an %ld 106 * format parameter that will be filled with REV. */ 107static svn_error_t * 108wrap_footer_error(svn_error_t *err, 109 const char *message, 110 svn_revnum_t rev) 111{ 112 if (err) 113 return svn_error_quick_wrapf(err, message, rev); 114 115 return SVN_NO_ERROR; 116} 117 118svn_error_t * 119svn_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, 124 svn_revnum_t rev, 125 apr_off_t footer_offset, 126 apr_pool_t *result_pool) 127{ 128 apr_int64_t val; 129 char *last_str = footer->data; 130 131 /* Get the L2P offset. */ 132 const char *str = svn_cstring_tokenize(" ", &last_str); 133 if (str == NULL) 134 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 135 "Invalid r%ld footer", rev); 136 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", 140 rev)); 141 *l2p_offset = (apr_off_t)val; 142 143 /* Get the L2P checksum. */ 144 str = svn_cstring_tokenize(" ", &last_str); 145 if (str == NULL) 146 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 147 "Invalid r%ld footer", rev); 148 149 SVN_ERR(svn_checksum_parse_hex(l2p_checksum, svn_checksum_md5, str, 150 result_pool)); 151 152 /* Get the P2L offset. */ 153 str = svn_cstring_tokenize(" ", &last_str); 154 if (str == NULL) 155 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 156 "Invalid r%ld footer", rev); 157 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", 161 rev)); 162 *p2l_offset = (apr_off_t)val; 163 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" 168 " in r%ld footer", 169 apr_psprintf(result_pool, 170 "0x%" APR_UINT64_T_HEX_FMT, 171 (apr_uint64_t)*p2l_offset), 172 apr_psprintf(result_pool, 173 "0x%" APR_UINT64_T_HEX_FMT, 174 (apr_uint64_t)*l2p_offset), 175 rev); 176 177 /* Get the P2L checksum. */ 178 str = svn_cstring_tokenize(" ", &last_str); 179 if (str == NULL) 180 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 181 "Invalid r%ld footer", rev); 182 183 SVN_ERR(svn_checksum_parse_hex(p2l_checksum, svn_checksum_md5, str, 184 result_pool)); 185 186 return SVN_NO_ERROR; 187} 188 189svn_stringbuf_t * 190svn_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) 196{ 197 return svn_stringbuf_createf(result_pool, 198 "%" APR_OFF_T_FMT " %s %" APR_OFF_T_FMT " %s", 199 l2p_offset, 200 svn_checksum_to_cstring(l2p_checksum, 201 scratch_pool), 202 p2l_offset, 203 svn_checksum_to_cstring(p2l_checksum, 204 scratch_pool)); 205} 206 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 210 RESULT_POOL. */ 211static svn_error_t * 212read_header_block(apr_hash_t **headers, 213 svn_stream_t *stream, 214 apr_pool_t *result_pool) 215{ 216 *headers = svn_hash__make(result_pool); 217 218 while (1) 219 { 220 svn_stringbuf_t *header_str; 221 const char *name, *value; 222 apr_size_t i = 0; 223 apr_size_t name_len; 224 svn_boolean_t eof; 225 226 SVN_ERR(svn_stream_readline(stream, &header_str, "\n", &eof, 227 result_pool)); 228 229 if (eof || header_str->len == 0) 230 break; /* end of header block */ 231 232 while (header_str->data[i] != ':') 233 { 234 if (header_str->data[i] == '\0') 235 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 236 _("Found malformed header '%s' in " 237 "revision file"), 238 header_str->data); 239 i++; 240 } 241 242 /* Create a 'name' string and point to it. */ 243 header_str->data[i] = '\0'; 244 name = header_str->data; 245 name_len = i; 246 247 /* Check if we have enough data to parse. */ 248 if (i + 2 > header_str->len) 249 { 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 " 254 "revision file"), 255 header_str->data); 256 } 257 258 /* Skip over the NULL byte and the space following it. */ 259 i += 2; 260 261 value = header_str->data + i; 262 263 /* header_str is safely in our pool, so we can use bits of it as 264 key and value. */ 265 apr_hash_set(*headers, name, name_len, value); 266 } 267 268 return SVN_NO_ERROR; 269} 270 271svn_error_t * 272svn_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) 276{ 277 svn_fs_x__representation_t *rep; 278 char *str; 279 apr_int64_t val; 280 char *string = text->data; 281 svn_checksum_t *checksum; 282 283 rep = apr_pcalloc(result_pool, sizeof(*rep)); 284 *rep_p = rep; 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(&rep->id.change_set, str)); 292 293 /* while in transactions, it is legal to simply write "-1" */ 294 if (rep->id.change_set == -1) 295 return SVN_NO_ERROR; 296 297 str = svn_cstring_tokenize(" ", &string); 298 if (str == NULL) 299 { 300 if (rep->id.change_set == SVN_FS_X__INVALID_CHANGE_SET) 301 return SVN_NO_ERROR; 302 303 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 304 _("Malformed text representation offset line in node-rev")); 305 } 306 307 SVN_ERR(svn_cstring_atoi64(&val, str)); 308 rep->id.number = (apr_off_t)val; 309 310 str = svn_cstring_tokenize(" ", &string); 311 if (str == NULL) 312 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 313 _("Malformed text representation offset line in node-rev")); 314 315 SVN_ERR(svn_cstring_atoi64(&val, str)); 316 rep->size = (svn_filesize_t)val; 317 318 str = svn_cstring_tokenize(" ", &string); 319 if (str == NULL) 320 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 321 _("Malformed text representation offset line in node-rev")); 322 323 SVN_ERR(svn_cstring_atoi64(&val, str)); 324 rep->expanded_size = (svn_filesize_t)val; 325 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")); 331 332 SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_md5, str, 333 scratch_pool)); 334 if (checksum) 335 memcpy(rep->md5_digest, checksum->digest, sizeof(rep->md5_digest)); 336 337 /* The remaining fields are only used for formats >= 4, so check that. */ 338 str = svn_cstring_tokenize(" ", &string); 339 if (str == NULL) 340 return SVN_NO_ERROR; 341 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")); 346 347 SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_sha1, str, 348 scratch_pool)); 349 rep->has_sha1 = checksum != NULL; 350 if (checksum) 351 memcpy(rep->sha1_digest, checksum->digest, sizeof(rep->sha1_digest)); 352 353 return SVN_NO_ERROR; 354} 355 356/* Wrap read_rep_offsets_body(), extracting its TXN_ID from our NODEREV_ID, 357 and adding an error message. */ 358static svn_error_t * 359read_rep_offsets(svn_fs_x__representation_t **rep_p, 360 char *string, 361 const svn_fs_x__id_t *noderev_id, 362 apr_pool_t *result_pool, 363 apr_pool_t *scratch_pool) 364{ 365 svn_error_t *err 366 = svn_fs_x__parse_representation(rep_p, 367 svn_stringbuf_create_wrap(string, 368 scratch_pool), 369 result_pool, 370 scratch_pool); 371 if (err) 372 { 373 const svn_string_t *id_unparsed; 374 const char *where; 375 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':"), 380 id_unparsed->data); 381 382 return svn_error_quick_wrap(err, where); 383 } 384 385 return SVN_NO_ERROR; 386} 387 388/* If PATH needs to be escaped, return an escaped version of it, allocated 389 * from RESULT_POOL. Otherwise, return PATH directly. */ 390static const char * 391auto_escape_path(const char *path, 392 apr_pool_t *result_pool) 393{ 394 apr_size_t len = strlen(path); 395 apr_size_t i; 396 const char esc = '\x1b'; 397 398 for (i = 0; i < len; ++i) 399 if (path[i] < ' ') 400 { 401 svn_stringbuf_t *escaped = svn_stringbuf_create_ensure(2 * len, 402 result_pool); 403 for (i = 0; i < len; ++i) 404 if (path[i] < ' ') 405 { 406 svn_stringbuf_appendbyte(escaped, esc); 407 svn_stringbuf_appendbyte(escaped, path[i] + 'A' - 1); 408 } 409 else 410 { 411 svn_stringbuf_appendbyte(escaped, path[i]); 412 } 413 414 return escaped->data; 415 } 416 417 return path; 418} 419 420/* If PATH has been escaped, return the un-escaped version of it, allocated 421 * from RESULT_POOL. Otherwise, return PATH directly. */ 422static const char * 423auto_unescape_path(const char *path, 424 apr_pool_t *result_pool) 425{ 426 const char esc = '\x1b'; 427 if (strchr(path, esc)) 428 { 429 apr_size_t len = strlen(path); 430 apr_size_t i; 431 432 svn_stringbuf_t *unescaped = svn_stringbuf_create_ensure(len, 433 result_pool); 434 for (i = 0; i < len; ++i) 435 if (path[i] == esc) 436 svn_stringbuf_appendbyte(unescaped, path[++i] + 1 - 'A'); 437 else 438 svn_stringbuf_appendbyte(unescaped, path[i]); 439 440 return unescaped->data; 441 } 442 443 return path; 444} 445 446/* Find entry HEADER_NAME in HEADERS and parse its value into *ID. */ 447static svn_error_t * 448read_id_part(svn_fs_x__id_t *id, 449 apr_hash_t *headers, 450 const char *header_name) 451{ 452 const char *value = svn_hash_gets(headers, header_name); 453 if (value == NULL) 454 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 455 _("Missing %s field in node-rev"), 456 header_name); 457 458 SVN_ERR(svn_fs_x__id_parse(id, value)); 459 return SVN_NO_ERROR; 460} 461 462svn_error_t * 463svn_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) 467{ 468 apr_hash_t *headers; 469 svn_fs_x__noderev_t *noderev; 470 char *value; 471 const char *noderev_id; 472 473 SVN_ERR(read_header_block(&headers, stream, scratch_pool)); 474 SVN_ERR(svn_stream_close(stream)); 475 476 noderev = apr_pcalloc(result_pool, sizeof(*noderev)); 477 478 /* for error messages later */ 479 noderev_id = svn_hash_gets(headers, HEADER_ID); 480 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)); 485 486 /* Read the type. */ 487 value = svn_hash_gets(headers, HEADER_TYPE); 488 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'"), 495 noderev_id); 496 497 noderev->kind = (strcmp(value, SVN_FS_X__KIND_FILE) == 0) 498 ? svn_node_file 499 : svn_node_dir; 500 501 /* Read the 'count' field. */ 502 value = svn_hash_gets(headers, HEADER_COUNT); 503 if (value) 504 SVN_ERR(svn_cstring_atoi(&noderev->predecessor_count, value)); 505 else 506 noderev->predecessor_count = 0; 507 508 /* Get the properties location. */ 509 value = svn_hash_gets(headers, HEADER_PROPS); 510 if (value) 511 { 512 SVN_ERR(read_rep_offsets(&noderev->prop_rep, value, 513 &noderev->noderev_id, result_pool, 514 scratch_pool)); 515 } 516 517 /* Get the data location. */ 518 value = svn_hash_gets(headers, HEADER_TEXT); 519 if (value) 520 { 521 SVN_ERR(read_rep_offsets(&noderev->data_rep, value, 522 &noderev->noderev_id, result_pool, 523 scratch_pool)); 524 } 525 526 /* Get the created path. */ 527 value = svn_hash_gets(headers, HEADER_CPATH); 528 if (value == NULL) 529 { 530 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 531 _("Missing cpath field in node-rev '%s'"), 532 noderev_id); 533 } 534 else 535 { 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'"), 539 noderev_id); 540 541 noderev->created_path = auto_unescape_path(apr_pstrdup(result_pool, 542 value), 543 result_pool); 544 } 545 546 /* Get the predecessor ID. */ 547 value = svn_hash_gets(headers, HEADER_PRED); 548 if (value) 549 SVN_ERR(svn_fs_x__id_parse(&noderev->predecessor_id, value)); 550 else 551 svn_fs_x__id_reset(&noderev->predecessor_id); 552 553 /* Get the copyroot. */ 554 value = svn_hash_gets(headers, HEADER_COPYROOT); 555 if (value == NULL) 556 { 557 noderev->copyroot_path = noderev->created_path; 558 noderev->copyroot_rev 559 = svn_fs_x__get_revnum(noderev->noderev_id.change_set); 560 } 561 else 562 { 563 SVN_ERR(parse_revnum(&noderev->copyroot_rev, (const char **)&value)); 564 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'"), 568 noderev_id); 569 noderev->copyroot_path = auto_unescape_path(apr_pstrdup(result_pool, 570 value), 571 result_pool); 572 } 573 574 /* Get the copyfrom. */ 575 value = svn_hash_gets(headers, HEADER_COPYFROM); 576 if (value == NULL) 577 { 578 noderev->copyfrom_path = NULL; 579 noderev->copyfrom_rev = SVN_INVALID_REVNUM; 580 } 581 else 582 { 583 SVN_ERR(parse_revnum(&noderev->copyfrom_rev, (const char **)&value)); 584 585 if (*value == 0) 586 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 587 _("Malformed copyfrom line in node-rev '%s'"), 588 noderev_id); 589 noderev->copyfrom_path = auto_unescape_path(apr_pstrdup(result_pool, 590 value), 591 result_pool); 592 } 593 594 /* Get the mergeinfo count. */ 595 value = svn_hash_gets(headers, HEADER_MINFO_CNT); 596 if (value) 597 SVN_ERR(svn_cstring_atoi64(&noderev->mergeinfo_count, value)); 598 else 599 noderev->mergeinfo_count = 0; 600 601 /* Get whether *this* node has mergeinfo. */ 602 value = svn_hash_gets(headers, HEADER_MINFO_HERE); 603 noderev->has_mergeinfo = (value != NULL); 604 605 *noderev_p = noderev; 606 607 return SVN_NO_ERROR; 608} 609 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. 613 */ 614static const char * 615format_digest(const unsigned char *digest, 616 svn_checksum_kind_t kind, 617 svn_boolean_t is_null, 618 apr_pool_t *result_pool) 619{ 620 svn_checksum_t checksum; 621 checksum.digest = digest; 622 checksum.kind = kind; 623 624 if (is_null) 625 return "(null)"; 626 627 return svn_checksum_to_cstring_display(&checksum, result_pool); 628} 629 630svn_stringbuf_t * 631svn_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) 635{ 636 if (!rep->has_sha1) 637 return svn_stringbuf_createf 638 (result_pool, 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, 642 rep->expanded_size, 643 format_digest(rep->md5_digest, svn_checksum_md5, FALSE, 644 scratch_pool)); 645 646 return svn_stringbuf_createf 647 (result_pool, 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, 651 rep->expanded_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)); 656} 657 658 659svn_error_t * 660svn_fs_x__write_noderev(svn_stream_t *outfile, 661 svn_fs_x__noderev_t *noderev, 662 apr_pool_t *scratch_pool) 663{ 664 svn_string_t *str_id; 665 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", 668 str_id->data)); 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", 671 str_id->data)); 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", 674 str_id->data)); 675 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)); 679 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)); 684 685 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COUNT ": %d\n", 686 noderev->predecessor_count)); 687 688 if (noderev->data_rep) 689 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_TEXT ": %s\n", 690 svn_fs_x__unparse_representation 691 (noderev->data_rep, 692 noderev->kind == svn_node_dir, 693 scratch_pool, scratch_pool)->data)); 694 695 if (noderev->prop_rep) 696 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_PROPS ": %s\n", 697 svn_fs_x__unparse_representation 698 (noderev->prop_rep, 699 TRUE, scratch_pool, scratch_pool)->data)); 700 701 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_CPATH ": %s\n", 702 auto_escape_path(noderev->created_path, 703 scratch_pool))); 704 705 if (noderev->copyfrom_path) 706 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COPYFROM ": %ld" 707 " %s\n", 708 noderev->copyfrom_rev, 709 auto_escape_path(noderev->copyfrom_path, 710 scratch_pool))); 711 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" 716 " %s\n", 717 noderev->copyroot_rev, 718 auto_escape_path(noderev->copyroot_path, 719 scratch_pool))); 720 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)); 725 726 if (noderev->has_mergeinfo) 727 SVN_ERR(svn_stream_puts(outfile, HEADER_MINFO_HERE ": y\n")); 728 729 return svn_stream_puts(outfile, "\n"); 730} 731 732svn_error_t * 733svn_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) 737{ 738 svn_stringbuf_t *buffer; 739 char *str, *last_str; 740 apr_int64_t val; 741 svn_boolean_t eol = FALSE; 742 743 SVN_ERR(svn_stream_readline(stream, &buffer, "\n", &eol, scratch_pool)); 744 745 *header = apr_pcalloc(result_pool, sizeof(**header)); 746 (*header)->header_size = buffer->len + 1; 747 if (strcmp(buffer->data, REP_DELTA) == 0) 748 { 749 /* This is a delta against the empty stream. */ 750 (*header)->type = svn_fs_x__rep_self_delta; 751 return SVN_NO_ERROR; 752 } 753 754 (*header)->type = svn_fs_x__rep_delta; 755 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)) 760 goto error; 761 762 SVN_ERR(parse_revnum(&(*header)->base_revision, (const char **)&last_str)); 763 764 str = svn_cstring_tokenize(" ", &last_str); 765 if (! str) 766 goto error; 767 SVN_ERR(svn_cstring_atoi64(&val, str)); 768 (*header)->base_item_index = (apr_off_t)val; 769 770 str = svn_cstring_tokenize(" ", &last_str); 771 if (! str) 772 goto error; 773 SVN_ERR(svn_cstring_atoi64(&val, str)); 774 (*header)->base_length = (svn_filesize_t)val; 775 776 return SVN_NO_ERROR; 777 778 error: 779 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 780 _("Malformed representation header")); 781} 782 783svn_error_t * 784svn_fs_x__write_rep_header(svn_fs_x__rep_header_t *header, 785 svn_stream_t *stream, 786 apr_pool_t *scratch_pool) 787{ 788 const char *text; 789 790 switch (header->type) 791 { 792 case svn_fs_x__rep_self_delta: 793 text = REP_DELTA "\n"; 794 break; 795 796 default: 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); 801 } 802 803 return svn_error_trace(svn_stream_puts(stream, text)); 804} 805 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. */ 809static svn_error_t * 810read_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) 814{ 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; 819 820 /* Default return value. */ 821 *change_p = NULL; 822 823 SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, scratch_pool)); 824 825 /* Check for a blank line. */ 826 if (eof || (line->len == 0)) 827 return SVN_NO_ERROR; 828 829 change = apr_pcalloc(result_pool, sizeof(*change)); 830 last_str = line->data; 831 832 /* Get the change type. */ 833 str = svn_cstring_tokenize(" ", &last_str); 834 if (str == NULL) 835 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 836 _("Invalid changes line in rev-file")); 837 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, '-'); 842 if (kind_str) 843 { 844 /* Cap off the end of "str" (the action). */ 845 *kind_str = '\0'; 846 kind_str++; 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; 851 else 852 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 853 _("Invalid changes line in rev-file")); 854 } 855 856 if (strcmp(str, ACTION_MODIFY) == 0) 857 { 858 change->change_kind = svn_fs_path_change_modify; 859 } 860 else if (strcmp(str, ACTION_ADD) == 0) 861 { 862 change->change_kind = svn_fs_path_change_add; 863 } 864 else if (strcmp(str, ACTION_DELETE) == 0) 865 { 866 change->change_kind = svn_fs_path_change_delete; 867 } 868 else if (strcmp(str, ACTION_REPLACE) == 0) 869 { 870 change->change_kind = svn_fs_path_change_replace; 871 } 872 else 873 { 874 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 875 _("Invalid change kind in rev file")); 876 } 877 878 /* Get the text-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->text_mod = TRUE; 887 } 888 else if (strcmp(str, FLAG_FALSE) == 0) 889 { 890 change->text_mod = FALSE; 891 } 892 else 893 { 894 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 895 _("Invalid text-mod flag in rev-file")); 896 } 897 898 /* Get the prop-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->prop_mod = TRUE; 907 } 908 else if (strcmp(str, FLAG_FALSE) == 0) 909 { 910 change->prop_mod = FALSE; 911 } 912 else 913 { 914 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 915 _("Invalid prop-mod flag in rev-file")); 916 } 917 918 /* Get the mergeinfo-mod flag. */ 919 str = svn_cstring_tokenize(" ", &last_str); 920 if (str == NULL) 921 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 922 _("Invalid changes line in rev-file")); 923 924 if (strcmp(str, FLAG_TRUE) == 0) 925 { 926 change->mergeinfo_mod = svn_tristate_true; 927 } 928 else if (strcmp(str, FLAG_FALSE) == 0) 929 { 930 change->mergeinfo_mod = svn_tristate_false; 931 } 932 else 933 { 934 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 935 _("Invalid mergeinfo-mod flag in rev-file")); 936 } 937 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")); 942 943 change->path.data = auto_unescape_path(apr_pstrmemdup(result_pool, 944 last_str, 945 strlen(last_str)), 946 result_pool); 947 change->path.len = strlen(change->path.data); 948 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) 953 { 954 change->copyfrom_rev = SVN_INVALID_REVNUM; 955 change->copyfrom_path = NULL; 956 } 957 else 958 { 959 last_str = line->data; 960 SVN_ERR(parse_revnum(&change->copyfrom_rev, (const char **)&last_str)); 961 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")); 965 966 change->copyfrom_path = auto_unescape_path(last_str, result_pool); 967 } 968 969 *change_p = change; 970 971 return SVN_NO_ERROR; 972} 973 974svn_error_t * 975svn_fs_x__read_changes(apr_array_header_t **changes, 976 svn_stream_t *stream, 977 int max_count, 978 apr_pool_t *result_pool, 979 apr_pool_t *scratch_pool) 980{ 981 apr_pool_t *iterpool; 982 983 /* Pre-allocate enough room for most change lists. 984 (will be auto-expanded as necessary). 985 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). 990 */ 991 *changes = apr_array_make(result_pool, 63, sizeof(svn_fs_x__change_t *)); 992 993 iterpool = svn_pool_create(scratch_pool); 994 for (; max_count > 0; --max_count) 995 { 996 svn_fs_x__change_t *change; 997 svn_pool_clear(iterpool); 998 SVN_ERR(read_change(&change, stream, result_pool, iterpool)); 999 if (!change) 1000 break; 1001 1002 APR_ARRAY_PUSH(*changes, svn_fs_x__change_t*) = change; 1003 } 1004 svn_pool_destroy(iterpool); 1005 1006 return SVN_NO_ERROR; 1007} 1008 1009svn_error_t * 1010svn_fs_x__read_changes_incrementally(svn_stream_t *stream, 1011 svn_fs_x__change_receiver_t 1012 change_receiver, 1013 void *change_receiver_baton, 1014 apr_pool_t *scratch_pool) 1015{ 1016 svn_fs_x__change_t *change; 1017 apr_pool_t *iterpool; 1018 1019 iterpool = svn_pool_create(scratch_pool); 1020 do 1021 { 1022 svn_pool_clear(iterpool); 1023 1024 SVN_ERR(read_change(&change, stream, iterpool, iterpool)); 1025 if (change) 1026 SVN_ERR(change_receiver(change_receiver_baton, change, iterpool)); 1027 } 1028 while (change); 1029 svn_pool_destroy(iterpool); 1030 1031 return SVN_NO_ERROR; 1032} 1033 1034/* Write a single change entry, path PATH, change CHANGE, to STREAM. 1035 1036 All temporary allocations are in SCRATCH_POOL. */ 1037static svn_error_t * 1038write_change_entry(svn_stream_t *stream, 1039 svn_fs_x__change_t *change, 1040 apr_pool_t *scratch_pool) 1041{ 1042 const char *change_string = NULL; 1043 const char *kind_string = ""; 1044 svn_stringbuf_t *buf; 1045 apr_size_t len; 1046 1047 switch (change->change_kind) 1048 { 1049 case svn_fs_path_change_modify: 1050 change_string = ACTION_MODIFY; 1051 break; 1052 case svn_fs_path_change_add: 1053 change_string = ACTION_ADD; 1054 break; 1055 case svn_fs_path_change_delete: 1056 change_string = ACTION_DELETE; 1057 break; 1058 case svn_fs_path_change_replace: 1059 change_string = ACTION_REPLACE; 1060 break; 1061 default: 1062 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 1063 _("Invalid change type %d"), 1064 change->change_kind); 1065 } 1066 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); 1073 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)); 1081 1082 if (SVN_IS_VALID_REVNUM(change->copyfrom_rev)) 1083 { 1084 svn_stringbuf_appendcstr(buf, apr_psprintf(scratch_pool, "%ld %s", 1085 change->copyfrom_rev, 1086 auto_escape_path(change->copyfrom_path, 1087 scratch_pool))); 1088 } 1089 1090 svn_stringbuf_appendbyte(buf, '\n'); 1091 1092 /* Write all change info in one write call. */ 1093 len = buf->len; 1094 return svn_error_trace(svn_stream_write(stream, buf->data, &len)); 1095} 1096 1097svn_error_t * 1098svn_fs_x__write_changes(svn_stream_t *stream, 1099 svn_fs_t *fs, 1100 apr_hash_t *changes, 1101 svn_boolean_t terminate_list, 1102 apr_pool_t *scratch_pool) 1103{ 1104 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1105 apr_array_header_t *sorted_changed_paths; 1106 int i; 1107 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. 1111 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. 1116 */ 1117 sorted_changed_paths = svn_sort__hash(changes, 1118 svn_sort_compare_items_lexically, 1119 scratch_pool); 1120 1121 /* Write all items to disk in the new order. */ 1122 for (i = 0; i < sorted_changed_paths->nelts; ++i) 1123 { 1124 svn_fs_x__change_t *change; 1125 1126 svn_pool_clear(iterpool); 1127 change = APR_ARRAY_IDX(sorted_changed_paths, i, svn_sort__item_t).value; 1128 1129 /* Write out the new entry into the final rev-file. */ 1130 SVN_ERR(write_change_entry(stream, change, iterpool)); 1131 } 1132 1133 if (terminate_list) 1134 SVN_ERR(svn_stream_puts(stream, "\n")); 1135 1136 svn_pool_destroy(iterpool); 1137 1138 return SVN_NO_ERROR; 1139} 1140 1141svn_error_t * 1142svn_fs_x__parse_properties(apr_hash_t **properties, 1143 const svn_string_t *content, 1144 apr_pool_t *result_pool) 1145{ 1146 const apr_byte_t *p = (const apr_byte_t *)content->data; 1147 const apr_byte_t *end = p + content->len; 1148 apr_uint64_t count; 1149 1150 *properties = apr_hash_make(result_pool); 1151 1152 /* Extract the number of properties we are expected to read. */ 1153 p = svn__decode_uint(&count, p, end); 1154 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. 1159 */ 1160 while (p < end) 1161 { 1162 apr_uint64_t value_len; 1163 svn_string_t *value; 1164 1165 const char *key = (const char *)p; 1166 1167 /* Note that this may never overflow / segfault because 1168 CONTENT itself is NUL-terminated. */ 1169 apr_size_t key_len = strlen(key); 1170 p += key_len + 1; 1171 if (key[key_len]) 1172 return svn_error_createf(SVN_ERR_FS_CORRUPT_PROPLIST, NULL, 1173 "Property name not NUL terminated"); 1174 1175 if (p >= end) 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"); 1182 1183 value = apr_pcalloc(result_pool, sizeof(*value)); 1184 value->data = (const char *)p; 1185 value->len = (apr_size_t)value_len; 1186 if (p[value->len]) 1187 return svn_error_createf(SVN_ERR_FS_CORRUPT_PROPLIST, NULL, 1188 "Property value not NUL terminated"); 1189 1190 p += value->len + 1; 1191 1192 apr_hash_set(*properties, key, key_len, value); 1193 } 1194 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"); 1199 1200 return SVN_NO_ERROR; 1201} 1202 1203svn_error_t * 1204svn_fs_x__write_properties(svn_stream_t *stream, 1205 apr_hash_t *proplist, 1206 apr_pool_t *scratch_pool) 1207{ 1208 apr_byte_t buffer[SVN__MAX_ENCODED_UINT_LEN]; 1209 apr_size_t len; 1210 apr_hash_index_t *hi; 1211 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)); 1215 1216 /* Serialize each property as follows: 1217 <Prop-name> <NUL> 1218 <Value-len> <Prop-value> <NUL> 1219 */ 1220 for (hi = apr_hash_first(scratch_pool, proplist); 1221 hi; 1222 hi = apr_hash_next(hi)) 1223 { 1224 const char *key; 1225 apr_size_t key_len; 1226 svn_string_t *value; 1227 apr_hash_this(hi, (const void **)&key, (apr_ssize_t *)&key_len, 1228 (void **)&value); 1229 1230 /* Include the terminating NUL. */ 1231 ++key_len; 1232 SVN_ERR(svn_stream_write(stream, key, &key_len)); 1233 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)); 1237 1238 /* Terminate with NUL. */ 1239 len = 1; 1240 SVN_ERR(svn_stream_write(stream, "", &len)); 1241 } 1242 1243 return SVN_NO_ERROR; 1244} 1245