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