low_level.c revision 309512
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 768 /* If STR is a all-zero checksum, CHECKSUM will be NULL and REP already 769 contains the correct value. */ 770 if (checksum) 771 memcpy(rep->md5_digest, checksum->digest, sizeof(rep->md5_digest)); 772 773 /* The remaining fields are only used for formats >= 4, so check that. */ 774 str = svn_cstring_tokenize(" ", &string); 775 if (str == NULL) 776 return SVN_NO_ERROR; 777 778 /* Read the SHA1 hash. */ 779 if (strlen(str) != (APR_SHA1_DIGESTSIZE * 2)) 780 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 781 _("Malformed text representation offset line in node-rev")); 782 783 SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_sha1, str, 784 scratch_pool)); 785 786 /* We do have a valid SHA1 but it might be all 0. 787 We cannot be sure where that came from (Alas! legacy), so let's not 788 claim we know the SHA1 in that case. */ 789 rep->has_sha1 = checksum != NULL; 790 791 /* If STR is a all-zero checksum, CHECKSUM will be NULL and REP already 792 contains the correct value. */ 793 if (checksum) 794 memcpy(rep->sha1_digest, checksum->digest, sizeof(rep->sha1_digest)); 795 796 /* Read the uniquifier. */ 797 str = svn_cstring_tokenize("/", &string); 798 if (str == NULL) 799 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 800 _("Malformed text representation offset line in node-rev")); 801 802 SVN_ERR(svn_fs_fs__id_txn_parse(&rep->uniquifier.noderev_txn_id, str)); 803 804 str = svn_cstring_tokenize(" ", &string); 805 if (str == NULL || *str != '_') 806 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 807 _("Malformed text representation offset line in node-rev")); 808 809 ++str; 810 rep->uniquifier.number = svn__base36toui64(&end, str); 811 812 if (*end) 813 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 814 _("Malformed text representation offset line in node-rev")); 815 816 return SVN_NO_ERROR; 817} 818 819/* Wrap svn_fs_fs__parse_representation(), extracting its TXN_ID from our 820 NODEREV_ID, and adding an error message. */ 821static svn_error_t * 822read_rep_offsets(representation_t **rep_p, 823 char *string, 824 const svn_fs_id_t *noderev_id, 825 apr_pool_t *result_pool, 826 apr_pool_t *scratch_pool) 827{ 828 svn_error_t *err 829 = svn_fs_fs__parse_representation(rep_p, 830 svn_stringbuf_create_wrap(string, 831 scratch_pool), 832 result_pool, 833 scratch_pool); 834 if (err) 835 { 836 const svn_string_t *id_unparsed; 837 const char *where; 838 839 id_unparsed = svn_fs_fs__id_unparse(noderev_id, scratch_pool); 840 where = apr_psprintf(scratch_pool, 841 _("While reading representation offsets " 842 "for node-revision '%s':"), 843 noderev_id ? id_unparsed->data : "(null)"); 844 845 return svn_error_quick_wrap(err, where); 846 } 847 848 if ((*rep_p)->revision == SVN_INVALID_REVNUM) 849 if (noderev_id) 850 (*rep_p)->txn_id = *svn_fs_fs__id_txn_id(noderev_id); 851 852 return SVN_NO_ERROR; 853} 854 855svn_error_t * 856svn_fs_fs__read_noderev(node_revision_t **noderev_p, 857 svn_stream_t *stream, 858 apr_pool_t *result_pool, 859 apr_pool_t *scratch_pool) 860{ 861 apr_hash_t *headers; 862 node_revision_t *noderev; 863 char *value; 864 const char *noderev_id; 865 866 SVN_ERR(read_header_block(&headers, stream, scratch_pool)); 867 868 noderev = apr_pcalloc(result_pool, sizeof(*noderev)); 869 870 /* Read the node-rev id. */ 871 value = svn_hash_gets(headers, HEADER_ID); 872 if (value == NULL) 873 /* ### More information: filename/offset coordinates */ 874 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, 875 _("Missing id field in node-rev")); 876 877 SVN_ERR(svn_stream_close(stream)); 878 879 SVN_ERR(svn_fs_fs__id_parse(&noderev->id, value, result_pool)); 880 noderev_id = value; /* for error messages later */ 881 882 /* Read the type. */ 883 value = svn_hash_gets(headers, HEADER_TYPE); 884 885 if ((value == NULL) || 886 ( strcmp(value, SVN_FS_FS__KIND_FILE) 887 && strcmp(value, SVN_FS_FS__KIND_DIR))) 888 /* ### s/kind/type/ */ 889 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 890 _("Missing kind field in node-rev '%s'"), 891 noderev_id); 892 893 noderev->kind = (strcmp(value, SVN_FS_FS__KIND_FILE) == 0) 894 ? svn_node_file 895 : svn_node_dir; 896 897 /* Read the 'count' field. */ 898 value = svn_hash_gets(headers, HEADER_COUNT); 899 if (value) 900 SVN_ERR(svn_cstring_atoi(&noderev->predecessor_count, value)); 901 else 902 noderev->predecessor_count = 0; 903 904 /* Get the properties location. */ 905 value = svn_hash_gets(headers, HEADER_PROPS); 906 if (value) 907 { 908 SVN_ERR(read_rep_offsets(&noderev->prop_rep, value, 909 noderev->id, result_pool, scratch_pool)); 910 } 911 912 /* Get the data location. */ 913 value = svn_hash_gets(headers, HEADER_TEXT); 914 if (value) 915 { 916 SVN_ERR(read_rep_offsets(&noderev->data_rep, value, 917 noderev->id, result_pool, scratch_pool)); 918 } 919 920 /* Get the created path. */ 921 value = svn_hash_gets(headers, HEADER_CPATH); 922 if (value == NULL) 923 { 924 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 925 _("Missing cpath field in node-rev '%s'"), 926 noderev_id); 927 } 928 else 929 { 930 if (!svn_fspath__is_canonical(value)) 931 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 932 _("Non-canonical cpath field in node-rev '%s'"), 933 noderev_id); 934 935 noderev->created_path = apr_pstrdup(result_pool, value); 936 } 937 938 /* Get the predecessor ID. */ 939 value = svn_hash_gets(headers, HEADER_PRED); 940 if (value) 941 SVN_ERR(svn_fs_fs__id_parse(&noderev->predecessor_id, value, 942 result_pool)); 943 944 /* Get the copyroot. */ 945 value = svn_hash_gets(headers, HEADER_COPYROOT); 946 if (value == NULL) 947 { 948 noderev->copyroot_path = apr_pstrdup(result_pool, noderev->created_path); 949 noderev->copyroot_rev = svn_fs_fs__id_rev(noderev->id); 950 } 951 else 952 { 953 SVN_ERR(parse_revnum(&noderev->copyroot_rev, (const char **)&value)); 954 955 if (!svn_fspath__is_canonical(value)) 956 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 957 _("Malformed copyroot line in node-rev '%s'"), 958 noderev_id); 959 noderev->copyroot_path = apr_pstrdup(result_pool, value); 960 } 961 962 /* Get the copyfrom. */ 963 value = svn_hash_gets(headers, HEADER_COPYFROM); 964 if (value == NULL) 965 { 966 noderev->copyfrom_path = NULL; 967 noderev->copyfrom_rev = SVN_INVALID_REVNUM; 968 } 969 else 970 { 971 SVN_ERR(parse_revnum(&noderev->copyfrom_rev, (const char **)&value)); 972 973 if (*value == 0) 974 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 975 _("Malformed copyfrom line in node-rev '%s'"), 976 noderev_id); 977 noderev->copyfrom_path = apr_pstrdup(result_pool, value); 978 } 979 980 /* Get whether this is a fresh txn root. */ 981 value = svn_hash_gets(headers, HEADER_FRESHTXNRT); 982 noderev->is_fresh_txn_root = (value != NULL); 983 984 /* Get the mergeinfo count. */ 985 value = svn_hash_gets(headers, HEADER_MINFO_CNT); 986 if (value) 987 SVN_ERR(svn_cstring_atoi64(&noderev->mergeinfo_count, value)); 988 else 989 noderev->mergeinfo_count = 0; 990 991 /* Get whether *this* node has mergeinfo. */ 992 value = svn_hash_gets(headers, HEADER_MINFO_HERE); 993 noderev->has_mergeinfo = (value != NULL); 994 995 *noderev_p = noderev; 996 997 return SVN_NO_ERROR; 998} 999 1000/* Return a textual representation of the DIGEST of given KIND. 1001 * If IS_NULL is TRUE, no digest is available. 1002 * Allocate the result in RESULT_POOL. 1003 */ 1004static const char * 1005format_digest(const unsigned char *digest, 1006 svn_checksum_kind_t kind, 1007 svn_boolean_t is_null, 1008 apr_pool_t *result_pool) 1009{ 1010 svn_checksum_t checksum; 1011 checksum.digest = digest; 1012 checksum.kind = kind; 1013 1014 if (is_null) 1015 return "(null)"; 1016 1017 return svn_checksum_to_cstring_display(&checksum, result_pool); 1018} 1019 1020svn_stringbuf_t * 1021svn_fs_fs__unparse_representation(representation_t *rep, 1022 int format, 1023 svn_boolean_t mutable_rep_truncated, 1024 apr_pool_t *result_pool, 1025 apr_pool_t *scratch_pool) 1026{ 1027 char buffer[SVN_INT64_BUFFER_SIZE]; 1028 if (svn_fs_fs__id_txn_used(&rep->txn_id) && mutable_rep_truncated) 1029 return svn_stringbuf_ncreate("-1", 2, result_pool); 1030 1031 if (format < SVN_FS_FS__MIN_REP_SHARING_FORMAT || !rep->has_sha1) 1032 return svn_stringbuf_createf 1033 (result_pool, "%ld %" APR_UINT64_T_FMT " %" SVN_FILESIZE_T_FMT 1034 " %" SVN_FILESIZE_T_FMT " %s", 1035 rep->revision, rep->item_index, rep->size, 1036 rep->expanded_size, 1037 format_digest(rep->md5_digest, svn_checksum_md5, FALSE, 1038 scratch_pool)); 1039 1040 svn__ui64tobase36(buffer, rep->uniquifier.number); 1041 return svn_stringbuf_createf 1042 (result_pool, "%ld %" APR_UINT64_T_FMT " %" SVN_FILESIZE_T_FMT 1043 " %" SVN_FILESIZE_T_FMT " %s %s %s/_%s", 1044 rep->revision, rep->item_index, rep->size, 1045 rep->expanded_size, 1046 format_digest(rep->md5_digest, svn_checksum_md5, 1047 FALSE, scratch_pool), 1048 format_digest(rep->sha1_digest, svn_checksum_sha1, 1049 !rep->has_sha1, scratch_pool), 1050 svn_fs_fs__id_txn_unparse(&rep->uniquifier.noderev_txn_id, 1051 scratch_pool), 1052 buffer); 1053} 1054 1055 1056svn_error_t * 1057svn_fs_fs__write_noderev(svn_stream_t *outfile, 1058 node_revision_t *noderev, 1059 int format, 1060 svn_boolean_t include_mergeinfo, 1061 apr_pool_t *scratch_pool) 1062{ 1063 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_ID ": %s\n", 1064 svn_fs_fs__id_unparse(noderev->id, 1065 scratch_pool)->data)); 1066 1067 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_TYPE ": %s\n", 1068 (noderev->kind == svn_node_file) ? 1069 SVN_FS_FS__KIND_FILE : SVN_FS_FS__KIND_DIR)); 1070 1071 if (noderev->predecessor_id) 1072 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_PRED ": %s\n", 1073 svn_fs_fs__id_unparse(noderev->predecessor_id, 1074 scratch_pool)->data)); 1075 1076 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COUNT ": %d\n", 1077 noderev->predecessor_count)); 1078 1079 if (noderev->data_rep) 1080 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_TEXT ": %s\n", 1081 svn_fs_fs__unparse_representation 1082 (noderev->data_rep, 1083 format, 1084 noderev->kind == svn_node_dir, 1085 scratch_pool, scratch_pool)->data)); 1086 1087 if (noderev->prop_rep) 1088 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_PROPS ": %s\n", 1089 svn_fs_fs__unparse_representation 1090 (noderev->prop_rep, format, 1091 TRUE, scratch_pool, scratch_pool)->data)); 1092 1093 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_CPATH ": %s\n", 1094 noderev->created_path)); 1095 1096 if (noderev->copyfrom_path) 1097 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COPYFROM ": %ld" 1098 " %s\n", 1099 noderev->copyfrom_rev, 1100 noderev->copyfrom_path)); 1101 1102 if ((noderev->copyroot_rev != svn_fs_fs__id_rev(noderev->id)) || 1103 (strcmp(noderev->copyroot_path, noderev->created_path) != 0)) 1104 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COPYROOT ": %ld" 1105 " %s\n", 1106 noderev->copyroot_rev, 1107 noderev->copyroot_path)); 1108 1109 if (noderev->is_fresh_txn_root) 1110 SVN_ERR(svn_stream_puts(outfile, HEADER_FRESHTXNRT ": y\n")); 1111 1112 if (include_mergeinfo) 1113 { 1114 if (noderev->mergeinfo_count > 0) 1115 SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_MINFO_CNT 1116 ": %" APR_INT64_T_FMT "\n", 1117 noderev->mergeinfo_count)); 1118 1119 if (noderev->has_mergeinfo) 1120 SVN_ERR(svn_stream_puts(outfile, HEADER_MINFO_HERE ": y\n")); 1121 } 1122 1123 return svn_stream_puts(outfile, "\n"); 1124} 1125 1126svn_error_t * 1127svn_fs_fs__read_rep_header(svn_fs_fs__rep_header_t **header, 1128 svn_stream_t *stream, 1129 apr_pool_t *result_pool, 1130 apr_pool_t *scratch_pool) 1131{ 1132 svn_stringbuf_t *buffer; 1133 char *str, *last_str; 1134 apr_int64_t val; 1135 svn_boolean_t eol = FALSE; 1136 1137 SVN_ERR(svn_stream_readline(stream, &buffer, "\n", &eol, scratch_pool)); 1138 1139 *header = apr_pcalloc(result_pool, sizeof(**header)); 1140 (*header)->header_size = buffer->len + 1; 1141 if (strcmp(buffer->data, REP_PLAIN) == 0) 1142 { 1143 (*header)->type = svn_fs_fs__rep_plain; 1144 return SVN_NO_ERROR; 1145 } 1146 1147 if (strcmp(buffer->data, REP_DELTA) == 0) 1148 { 1149 /* This is a delta against the empty stream. */ 1150 (*header)->type = svn_fs_fs__rep_self_delta; 1151 return SVN_NO_ERROR; 1152 } 1153 1154 (*header)->type = svn_fs_fs__rep_delta; 1155 1156 /* We have hopefully a DELTA vs. a non-empty base revision. */ 1157 last_str = buffer->data; 1158 str = svn_cstring_tokenize(" ", &last_str); 1159 if (! str || (strcmp(str, REP_DELTA) != 0)) 1160 goto error; 1161 1162 SVN_ERR(parse_revnum(&(*header)->base_revision, (const char **)&last_str)); 1163 1164 str = svn_cstring_tokenize(" ", &last_str); 1165 if (! str) 1166 goto error; 1167 SVN_ERR(svn_cstring_atoi64(&val, str)); 1168 (*header)->base_item_index = (apr_off_t)val; 1169 1170 str = svn_cstring_tokenize(" ", &last_str); 1171 if (! str) 1172 goto error; 1173 SVN_ERR(svn_cstring_atoi64(&val, str)); 1174 (*header)->base_length = (svn_filesize_t)val; 1175 1176 return SVN_NO_ERROR; 1177 1178 error: 1179 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 1180 _("Malformed representation header")); 1181} 1182 1183svn_error_t * 1184svn_fs_fs__write_rep_header(svn_fs_fs__rep_header_t *header, 1185 svn_stream_t *stream, 1186 apr_pool_t *scratch_pool) 1187{ 1188 const char *text; 1189 1190 switch (header->type) 1191 { 1192 case svn_fs_fs__rep_plain: 1193 text = REP_PLAIN "\n"; 1194 break; 1195 1196 case svn_fs_fs__rep_self_delta: 1197 text = REP_DELTA "\n"; 1198 break; 1199 1200 default: 1201 text = apr_psprintf(scratch_pool, REP_DELTA " %ld %" APR_OFF_T_FMT 1202 " %" SVN_FILESIZE_T_FMT "\n", 1203 header->base_revision, header->base_item_index, 1204 header->base_length); 1205 } 1206 1207 return svn_error_trace(svn_stream_puts(stream, text)); 1208} 1209