1/* dag.c : DAG-like interface filesystem, private to libsvn_fs 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 <string.h> 24 25#include "svn_path.h" 26#include "svn_time.h" 27#include "svn_error.h" 28#include "svn_fs.h" 29#include "svn_hash.h" 30#include "svn_props.h" 31#include "svn_pools.h" 32 33#include "dag.h" 34#include "err.h" 35#include "fs.h" 36#include "key-gen.h" 37#include "node-rev.h" 38#include "trail.h" 39#include "reps-strings.h" 40#include "revs-txns.h" 41#include "id.h" 42 43#include "util/fs_skels.h" 44 45#include "bdb/txn-table.h" 46#include "bdb/rev-table.h" 47#include "bdb/nodes-table.h" 48#include "bdb/copies-table.h" 49#include "bdb/reps-table.h" 50#include "bdb/strings-table.h" 51#include "bdb/checksum-reps-table.h" 52#include "bdb/changes-table.h" 53#include "bdb/node-origins-table.h" 54 55#include "private/svn_skel.h" 56#include "private/svn_fs_util.h" 57#include "private/svn_fspath.h" 58#include "../libsvn_fs/fs-loader.h" 59 60#include "svn_private_config.h" 61 62 63/* Initializing a filesystem. */ 64 65struct dag_node_t 66{ 67 /*** NOTE: Keeping in-memory representations of disk data that can 68 be changed by other accessors is a nasty business. Such 69 representations are basically a cache with some pretty complex 70 invalidation rules. For example, the "node revision" 71 associated with a DAG node ID can look completely different to 72 a process that has modified that information as part of a 73 Berkeley DB transaction than it does to some other process. 74 That said, there are some aspects of a "node revision" which 75 never change, like its 'id' or 'kind'. Our best bet is to 76 limit ourselves to exposing outside of this interface only 77 those immutable aspects of a DAG node representation. ***/ 78 79 /* The filesystem this dag node came from. */ 80 svn_fs_t *fs; 81 82 /* The node revision ID for this dag node. */ 83 svn_fs_id_t *id; 84 85 /* The node's type (file, dir, etc.) */ 86 svn_node_kind_t kind; 87 88 /* the path at which this node was created. */ 89 const char *created_path; 90}; 91 92 93 94/* Trivial helper/accessor functions. */ 95svn_node_kind_t svn_fs_base__dag_node_kind(dag_node_t *node) 96{ 97 return node->kind; 98} 99 100 101const svn_fs_id_t * 102svn_fs_base__dag_get_id(dag_node_t *node) 103{ 104 return node->id; 105} 106 107 108const char * 109svn_fs_base__dag_get_created_path(dag_node_t *node) 110{ 111 return node->created_path; 112} 113 114 115svn_fs_t * 116svn_fs_base__dag_get_fs(dag_node_t *node) 117{ 118 return node->fs; 119} 120 121 122svn_boolean_t svn_fs_base__dag_check_mutable(dag_node_t *node, 123 const char *txn_id) 124{ 125 return (strcmp(svn_fs_base__id_txn_id(svn_fs_base__dag_get_id(node)), 126 txn_id) == 0); 127} 128 129 130svn_error_t * 131svn_fs_base__dag_get_node(dag_node_t **node, 132 svn_fs_t *fs, 133 const svn_fs_id_t *id, 134 trail_t *trail, 135 apr_pool_t *pool) 136{ 137 dag_node_t *new_node; 138 node_revision_t *noderev; 139 140 /* Construct the node. */ 141 new_node = apr_pcalloc(pool, sizeof(*new_node)); 142 new_node->fs = fs; 143 new_node->id = svn_fs_base__id_copy(id, pool); 144 145 /* Grab the contents so we can cache some of the immutable parts of it. */ 146 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, id, trail, pool)); 147 148 /* Initialize the KIND and CREATED_PATH attributes */ 149 new_node->kind = noderev->kind; 150 new_node->created_path = noderev->created_path; 151 152 /* Return a fresh new node */ 153 *node = new_node; 154 return SVN_NO_ERROR; 155} 156 157 158svn_error_t * 159svn_fs_base__dag_get_revision(svn_revnum_t *rev, 160 dag_node_t *node, 161 trail_t *trail, 162 apr_pool_t *pool) 163{ 164 /* Use the txn ID from the NODE's id to look up the transaction and 165 get its revision number. */ 166 return svn_fs_base__txn_get_revision 167 (rev, svn_fs_base__dag_get_fs(node), 168 svn_fs_base__id_txn_id(svn_fs_base__dag_get_id(node)), trail, pool); 169} 170 171 172svn_error_t * 173svn_fs_base__dag_get_predecessor_id(const svn_fs_id_t **id_p, 174 dag_node_t *node, 175 trail_t *trail, 176 apr_pool_t *pool) 177{ 178 node_revision_t *noderev; 179 180 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, node->fs, node->id, 181 trail, pool)); 182 *id_p = noderev->predecessor_id; 183 return SVN_NO_ERROR; 184} 185 186 187svn_error_t * 188svn_fs_base__dag_get_predecessor_count(int *count, 189 dag_node_t *node, 190 trail_t *trail, 191 apr_pool_t *pool) 192{ 193 node_revision_t *noderev; 194 195 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, node->fs, node->id, 196 trail, pool)); 197 *count = noderev->predecessor_count; 198 return SVN_NO_ERROR; 199} 200 201 202/* Trail body for svn_fs_base__dag_init_fs. */ 203static svn_error_t * 204txn_body_dag_init_fs(void *baton, 205 trail_t *trail) 206{ 207 node_revision_t noderev; 208 revision_t revision; 209 svn_revnum_t rev = SVN_INVALID_REVNUM; 210 svn_fs_t *fs = trail->fs; 211 svn_string_t date; 212 const char *txn_id; 213 const char *copy_id; 214 svn_fs_id_t *root_id = svn_fs_base__id_create("0", "0", "0", trail->pool); 215 216 /* Create empty root directory with node revision 0.0.0. */ 217 memset(&noderev, 0, sizeof(noderev)); 218 noderev.kind = svn_node_dir; 219 noderev.created_path = "/"; 220 SVN_ERR(svn_fs_bdb__put_node_revision(fs, root_id, &noderev, 221 trail, trail->pool)); 222 223 /* Create a new transaction (better have an id of "0") */ 224 SVN_ERR(svn_fs_bdb__create_txn(&txn_id, fs, root_id, trail, trail->pool)); 225 if (strcmp(txn_id, "0")) 226 return svn_error_createf 227 (SVN_ERR_FS_CORRUPT, 0, 228 _("Corrupt DB: initial transaction id not '0' in filesystem '%s'"), 229 fs->path); 230 231 /* Create a default copy (better have an id of "0") */ 232 SVN_ERR(svn_fs_bdb__reserve_copy_id(©_id, fs, trail, trail->pool)); 233 if (strcmp(copy_id, "0")) 234 return svn_error_createf 235 (SVN_ERR_FS_CORRUPT, 0, 236 _("Corrupt DB: initial copy id not '0' in filesystem '%s'"), fs->path); 237 SVN_ERR(svn_fs_bdb__create_copy(fs, copy_id, NULL, NULL, root_id, 238 copy_kind_real, trail, trail->pool)); 239 240 /* Link it into filesystem revision 0. */ 241 revision.txn_id = txn_id; 242 SVN_ERR(svn_fs_bdb__put_rev(&rev, fs, &revision, trail, trail->pool)); 243 if (rev != 0) 244 return svn_error_createf(SVN_ERR_FS_CORRUPT, 0, 245 _("Corrupt DB: initial revision number " 246 "is not '0' in filesystem '%s'"), fs->path); 247 248 /* Promote our transaction to a "committed" transaction. */ 249 SVN_ERR(svn_fs_base__txn_make_committed(fs, txn_id, rev, 250 trail, trail->pool)); 251 252 /* Set a date on revision 0. */ 253 date.data = svn_time_to_cstring(apr_time_now(), trail->pool); 254 date.len = strlen(date.data); 255 return svn_fs_base__set_rev_prop(fs, 0, SVN_PROP_REVISION_DATE, NULL, &date, 256 trail, trail->pool); 257} 258 259 260svn_error_t * 261svn_fs_base__dag_init_fs(svn_fs_t *fs) 262{ 263 return svn_fs_base__retry_txn(fs, txn_body_dag_init_fs, NULL, 264 TRUE, fs->pool); 265} 266 267 268 269/*** Directory node functions ***/ 270 271/* Some of these are helpers for functions outside this section. */ 272 273/* Given directory NODEREV in FS, set *ENTRIES_P to its entries list 274 hash, as part of TRAIL, or to NULL if NODEREV has no entries. The 275 entries list will be allocated in POOL, and the entries in that 276 list will not have interesting value in their 'kind' fields. If 277 NODEREV is not a directory, return the error SVN_ERR_FS_NOT_DIRECTORY. */ 278static svn_error_t * 279get_dir_entries(apr_hash_t **entries_p, 280 svn_fs_t *fs, 281 node_revision_t *noderev, 282 trail_t *trail, 283 apr_pool_t *pool) 284{ 285 apr_hash_t *entries = NULL; 286 apr_hash_index_t *hi; 287 svn_string_t entries_raw; 288 svn_skel_t *entries_skel; 289 290 /* Error if this is not a directory. */ 291 if (noderev->kind != svn_node_dir) 292 return svn_error_create 293 (SVN_ERR_FS_NOT_DIRECTORY, NULL, 294 _("Attempted to get entries of a non-directory node")); 295 296 /* If there's a DATA-KEY, there might be entries to fetch. */ 297 if (noderev->data_key) 298 { 299 /* Now we have a rep, follow through to get the entries. */ 300 SVN_ERR(svn_fs_base__rep_contents(&entries_raw, fs, noderev->data_key, 301 trail, pool)); 302 entries_skel = svn_skel__parse(entries_raw.data, entries_raw.len, pool); 303 304 /* Were there entries? Make a hash from them. */ 305 if (entries_skel) 306 SVN_ERR(svn_fs_base__parse_entries_skel(&entries, entries_skel, 307 pool)); 308 } 309 310 /* No hash? No problem. */ 311 *entries_p = NULL; 312 if (! entries) 313 return SVN_NO_ERROR; 314 315 /* Else, convert the hash from a name->id mapping to a name->dirent one. */ 316 *entries_p = apr_hash_make(pool); 317 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi)) 318 { 319 const void *key; 320 apr_ssize_t klen; 321 void *val; 322 svn_fs_dirent_t *dirent = apr_palloc(pool, sizeof(*dirent)); 323 324 /* KEY will be the entry name in ancestor, VAL the id. */ 325 apr_hash_this(hi, &key, &klen, &val); 326 dirent->name = key; 327 dirent->id = val; 328 dirent->kind = svn_node_unknown; 329 apr_hash_set(*entries_p, key, klen, dirent); 330 } 331 332 /* Return our findings. */ 333 return SVN_NO_ERROR; 334} 335 336 337/* Set *ID_P to the node-id for entry NAME in PARENT, as part of 338 TRAIL. If no such entry, set *ID_P to NULL but do not error. The 339 entry is allocated in POOL or in the same pool as PARENT; 340 the caller should copy if it cares. */ 341static svn_error_t * 342dir_entry_id_from_node(const svn_fs_id_t **id_p, 343 dag_node_t *parent, 344 const char *name, 345 trail_t *trail, 346 apr_pool_t *pool) 347{ 348 apr_hash_t *entries; 349 svn_fs_dirent_t *dirent; 350 351 SVN_ERR(svn_fs_base__dag_dir_entries(&entries, parent, trail, pool)); 352 if (entries) 353 dirent = svn_hash_gets(entries, name); 354 else 355 dirent = NULL; 356 357 *id_p = dirent ? dirent->id : NULL; 358 return SVN_NO_ERROR; 359} 360 361 362/* Add or set in PARENT a directory entry NAME pointing to ID. 363 Allocations are done in TRAIL. 364 365 Assumptions: 366 - PARENT is a mutable directory. 367 - ID does not refer to an ancestor of parent 368 - NAME is a single path component 369*/ 370static svn_error_t * 371set_entry(dag_node_t *parent, 372 const char *name, 373 const svn_fs_id_t *id, 374 const char *txn_id, 375 trail_t *trail, 376 apr_pool_t *pool) 377{ 378 node_revision_t *parent_noderev; 379 const char *rep_key, *mutable_rep_key; 380 apr_hash_t *entries = NULL; 381 svn_stream_t *wstream; 382 apr_size_t len; 383 svn_string_t raw_entries; 384 svn_stringbuf_t *raw_entries_buf; 385 svn_skel_t *entries_skel; 386 svn_fs_t *fs = svn_fs_base__dag_get_fs(parent); 387 388 /* Get the parent's node-revision. */ 389 SVN_ERR(svn_fs_bdb__get_node_revision(&parent_noderev, fs, parent->id, 390 trail, pool)); 391 rep_key = parent_noderev->data_key; 392 SVN_ERR(svn_fs_base__get_mutable_rep(&mutable_rep_key, rep_key, 393 fs, txn_id, trail, pool)); 394 395 /* If the parent node already pointed at a mutable representation, 396 we don't need to do anything. But if it didn't, either because 397 the parent didn't refer to any rep yet or because it referred to 398 an immutable one, we must make the parent refer to the mutable 399 rep we just created. */ 400 if (! svn_fs_base__same_keys(rep_key, mutable_rep_key)) 401 { 402 parent_noderev->data_key = mutable_rep_key; 403 SVN_ERR(svn_fs_bdb__put_node_revision(fs, parent->id, parent_noderev, 404 trail, pool)); 405 } 406 407 /* If the new representation inherited nothing, start a new entries 408 list for it. Else, go read its existing entries list. */ 409 if (rep_key) 410 { 411 SVN_ERR(svn_fs_base__rep_contents(&raw_entries, fs, rep_key, 412 trail, pool)); 413 entries_skel = svn_skel__parse(raw_entries.data, raw_entries.len, pool); 414 if (entries_skel) 415 SVN_ERR(svn_fs_base__parse_entries_skel(&entries, entries_skel, 416 pool)); 417 } 418 419 /* If we still have no ENTRIES hash, make one here. */ 420 if (! entries) 421 entries = apr_hash_make(pool); 422 423 /* Now, add our new entry to the entries list. */ 424 svn_hash_sets(entries, name, id); 425 426 /* Finally, replace the old entries list with the new one. */ 427 SVN_ERR(svn_fs_base__unparse_entries_skel(&entries_skel, entries, 428 pool)); 429 raw_entries_buf = svn_skel__unparse(entries_skel, pool); 430 SVN_ERR(svn_fs_base__rep_contents_write_stream(&wstream, fs, 431 mutable_rep_key, txn_id, 432 TRUE, trail, pool)); 433 len = raw_entries_buf->len; 434 SVN_ERR(svn_stream_write(wstream, raw_entries_buf->data, &len)); 435 return svn_stream_close(wstream); 436} 437 438 439/* Make a new entry named NAME in PARENT, as part of TRAIL. If IS_DIR 440 is true, then the node revision the new entry points to will be a 441 directory, else it will be a file. The new node will be allocated 442 in POOL. PARENT must be mutable, and must not have an entry 443 named NAME. */ 444static svn_error_t * 445make_entry(dag_node_t **child_p, 446 dag_node_t *parent, 447 const char *parent_path, 448 const char *name, 449 svn_boolean_t is_dir, 450 const char *txn_id, 451 trail_t *trail, 452 apr_pool_t *pool) 453{ 454 const svn_fs_id_t *new_node_id; 455 node_revision_t new_noderev; 456 457 /* Make sure that NAME is a single path component. */ 458 if (! svn_path_is_single_path_component(name)) 459 return svn_error_createf 460 (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL, 461 _("Attempted to create a node with an illegal name '%s'"), name); 462 463 /* Make sure that parent is a directory */ 464 if (parent->kind != svn_node_dir) 465 return svn_error_create 466 (SVN_ERR_FS_NOT_DIRECTORY, NULL, 467 _("Attempted to create entry in non-directory parent")); 468 469 /* Check that the parent is mutable. */ 470 if (! svn_fs_base__dag_check_mutable(parent, txn_id)) 471 return svn_error_createf 472 (SVN_ERR_FS_NOT_MUTABLE, NULL, 473 _("Attempted to clone child of non-mutable node")); 474 475 /* Check that parent does not already have an entry named NAME. */ 476 SVN_ERR(dir_entry_id_from_node(&new_node_id, parent, name, trail, pool)); 477 if (new_node_id) 478 return svn_error_createf 479 (SVN_ERR_FS_ALREADY_EXISTS, NULL, 480 _("Attempted to create entry that already exists")); 481 482 /* Create the new node's NODE-REVISION */ 483 memset(&new_noderev, 0, sizeof(new_noderev)); 484 new_noderev.kind = is_dir ? svn_node_dir : svn_node_file; 485 new_noderev.created_path = svn_fspath__join(parent_path, name, pool); 486 SVN_ERR(svn_fs_base__create_node 487 (&new_node_id, svn_fs_base__dag_get_fs(parent), &new_noderev, 488 svn_fs_base__id_copy_id(svn_fs_base__dag_get_id(parent)), 489 txn_id, trail, pool)); 490 491 /* Create a new dag_node_t for our new node */ 492 SVN_ERR(svn_fs_base__dag_get_node(child_p, 493 svn_fs_base__dag_get_fs(parent), 494 new_node_id, trail, pool)); 495 496 /* We can safely call set_entry because we already know that 497 PARENT is mutable, and we just created CHILD, so we know it has 498 no ancestors (therefore, PARENT cannot be an ancestor of CHILD) */ 499 return set_entry(parent, name, svn_fs_base__dag_get_id(*child_p), 500 txn_id, trail, pool); 501} 502 503 504svn_error_t * 505svn_fs_base__dag_dir_entries(apr_hash_t **entries, 506 dag_node_t *node, 507 trail_t *trail, 508 apr_pool_t *pool) 509{ 510 node_revision_t *noderev; 511 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, node->fs, node->id, 512 trail, pool)); 513 return get_dir_entries(entries, node->fs, noderev, trail, pool); 514} 515 516 517svn_error_t * 518svn_fs_base__dag_set_entry(dag_node_t *node, 519 const char *entry_name, 520 const svn_fs_id_t *id, 521 const char *txn_id, 522 trail_t *trail, 523 apr_pool_t *pool) 524{ 525 /* Check it's a directory. */ 526 if (node->kind != svn_node_dir) 527 return svn_error_create 528 (SVN_ERR_FS_NOT_DIRECTORY, NULL, 529 _("Attempted to set entry in non-directory node")); 530 531 /* Check it's mutable. */ 532 if (! svn_fs_base__dag_check_mutable(node, txn_id)) 533 return svn_error_create 534 (SVN_ERR_FS_NOT_MUTABLE, NULL, 535 _("Attempted to set entry in immutable node")); 536 537 return set_entry(node, entry_name, id, txn_id, trail, pool); 538} 539 540 541 542/*** Proplists. ***/ 543 544svn_error_t * 545svn_fs_base__dag_get_proplist(apr_hash_t **proplist_p, 546 dag_node_t *node, 547 trail_t *trail, 548 apr_pool_t *pool) 549{ 550 node_revision_t *noderev; 551 apr_hash_t *proplist = NULL; 552 svn_string_t raw_proplist; 553 svn_skel_t *proplist_skel; 554 555 /* Go get a fresh NODE-REVISION for this node. */ 556 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, node->fs, node->id, 557 trail, pool)); 558 559 /* Get property key (returning early if there isn't one) . */ 560 if (! noderev->prop_key) 561 { 562 *proplist_p = NULL; 563 return SVN_NO_ERROR; 564 } 565 566 /* Get the string associated with the property rep, parsing it as a 567 skel, and then attempt to parse *that* into a property hash. */ 568 SVN_ERR(svn_fs_base__rep_contents(&raw_proplist, 569 svn_fs_base__dag_get_fs(node), 570 noderev->prop_key, trail, pool)); 571 proplist_skel = svn_skel__parse(raw_proplist.data, raw_proplist.len, pool); 572 if (proplist_skel) 573 SVN_ERR(svn_skel__parse_proplist(&proplist, proplist_skel, pool)); 574 575 *proplist_p = proplist; 576 return SVN_NO_ERROR; 577} 578 579 580svn_error_t * 581svn_fs_base__dag_set_proplist(dag_node_t *node, 582 const apr_hash_t *proplist, 583 const char *txn_id, 584 trail_t *trail, 585 apr_pool_t *pool) 586{ 587 node_revision_t *noderev; 588 const char *rep_key, *mutable_rep_key; 589 svn_fs_t *fs = svn_fs_base__dag_get_fs(node); 590 svn_stream_t *wstream; 591 apr_size_t len; 592 svn_skel_t *proplist_skel; 593 svn_stringbuf_t *raw_proplist_buf; 594 base_fs_data_t *bfd = fs->fsap_data; 595 596 /* Sanity check: this node better be mutable! */ 597 if (! svn_fs_base__dag_check_mutable(node, txn_id)) 598 { 599 svn_string_t *idstr = svn_fs_base__id_unparse(node->id, pool); 600 return svn_error_createf 601 (SVN_ERR_FS_NOT_MUTABLE, NULL, 602 _("Can't set proplist on *immutable* node-revision %s"), 603 idstr->data); 604 } 605 606 /* Go get a fresh NODE-REVISION for this node. */ 607 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, node->id, 608 trail, pool)); 609 rep_key = noderev->prop_key; 610 611 /* Flatten the proplist into a string. */ 612 SVN_ERR(svn_skel__unparse_proplist(&proplist_skel, proplist, pool)); 613 raw_proplist_buf = svn_skel__unparse(proplist_skel, pool); 614 615 /* If this repository supports representation sharing, and the 616 resulting property list is exactly the same as another string in 617 the database, just use the previously existing string and get 618 outta here. */ 619 if (bfd->format >= SVN_FS_BASE__MIN_REP_SHARING_FORMAT) 620 { 621 svn_error_t *err; 622 const char *dup_rep_key; 623 svn_checksum_t *checksum; 624 625 SVN_ERR(svn_checksum(&checksum, svn_checksum_sha1, raw_proplist_buf->data, 626 raw_proplist_buf->len, pool)); 627 628 err = svn_fs_bdb__get_checksum_rep(&dup_rep_key, fs, checksum, 629 trail, pool); 630 if (! err) 631 { 632 if (noderev->prop_key) 633 SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->prop_key, 634 txn_id, trail, pool)); 635 noderev->prop_key = dup_rep_key; 636 return svn_fs_bdb__put_node_revision(fs, node->id, noderev, 637 trail, pool); 638 } 639 else if (err) 640 { 641 if (err->apr_err != SVN_ERR_FS_NO_SUCH_CHECKSUM_REP) 642 return svn_error_trace(err); 643 644 svn_error_clear(err); 645 err = SVN_NO_ERROR; 646 } 647 } 648 649 /* Get a mutable version of this rep (updating the node revision if 650 this isn't a NOOP) */ 651 SVN_ERR(svn_fs_base__get_mutable_rep(&mutable_rep_key, rep_key, 652 fs, txn_id, trail, pool)); 653 if (! svn_fs_base__same_keys(mutable_rep_key, rep_key)) 654 { 655 noderev->prop_key = mutable_rep_key; 656 SVN_ERR(svn_fs_bdb__put_node_revision(fs, node->id, noderev, 657 trail, pool)); 658 } 659 660 /* Replace the old property list with the new one. */ 661 SVN_ERR(svn_fs_base__rep_contents_write_stream(&wstream, fs, 662 mutable_rep_key, txn_id, 663 TRUE, trail, pool)); 664 len = raw_proplist_buf->len; 665 SVN_ERR(svn_stream_write(wstream, raw_proplist_buf->data, &len)); 666 SVN_ERR(svn_stream_close(wstream)); 667 668 return SVN_NO_ERROR; 669} 670 671 672 673/*** Roots. ***/ 674 675svn_error_t * 676svn_fs_base__dag_revision_root(dag_node_t **node_p, 677 svn_fs_t *fs, 678 svn_revnum_t rev, 679 trail_t *trail, 680 apr_pool_t *pool) 681{ 682 const svn_fs_id_t *root_id; 683 684 SVN_ERR(svn_fs_base__rev_get_root(&root_id, fs, rev, trail, pool)); 685 return svn_fs_base__dag_get_node(node_p, fs, root_id, trail, pool); 686} 687 688 689svn_error_t * 690svn_fs_base__dag_txn_root(dag_node_t **node_p, 691 svn_fs_t *fs, 692 const char *txn_id, 693 trail_t *trail, 694 apr_pool_t *pool) 695{ 696 const svn_fs_id_t *root_id, *ignored; 697 698 SVN_ERR(svn_fs_base__get_txn_ids(&root_id, &ignored, fs, txn_id, 699 trail, pool)); 700 return svn_fs_base__dag_get_node(node_p, fs, root_id, trail, pool); 701} 702 703 704svn_error_t * 705svn_fs_base__dag_txn_base_root(dag_node_t **node_p, 706 svn_fs_t *fs, 707 const char *txn_id, 708 trail_t *trail, 709 apr_pool_t *pool) 710{ 711 const svn_fs_id_t *base_root_id, *ignored; 712 713 SVN_ERR(svn_fs_base__get_txn_ids(&ignored, &base_root_id, fs, txn_id, 714 trail, pool)); 715 return svn_fs_base__dag_get_node(node_p, fs, base_root_id, trail, pool); 716} 717 718 719svn_error_t * 720svn_fs_base__dag_clone_child(dag_node_t **child_p, 721 dag_node_t *parent, 722 const char *parent_path, 723 const char *name, 724 const char *copy_id, 725 const char *txn_id, 726 trail_t *trail, 727 apr_pool_t *pool) 728{ 729 dag_node_t *cur_entry; /* parent's current entry named NAME */ 730 const svn_fs_id_t *new_node_id; /* node id we'll put into NEW_NODE */ 731 svn_fs_t *fs = svn_fs_base__dag_get_fs(parent); 732 733 /* First check that the parent is mutable. */ 734 if (! svn_fs_base__dag_check_mutable(parent, txn_id)) 735 return svn_error_createf 736 (SVN_ERR_FS_NOT_MUTABLE, NULL, 737 _("Attempted to clone child of non-mutable node")); 738 739 /* Make sure that NAME is a single path component. */ 740 if (! svn_path_is_single_path_component(name)) 741 return svn_error_createf 742 (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL, 743 _("Attempted to make a child clone with an illegal name '%s'"), name); 744 745 /* Find the node named NAME in PARENT's entries list if it exists. */ 746 SVN_ERR(svn_fs_base__dag_open(&cur_entry, parent, name, trail, pool)); 747 748 /* Check for mutability in the node we found. If it's mutable, we 749 don't need to clone it. */ 750 if (svn_fs_base__dag_check_mutable(cur_entry, txn_id)) 751 { 752 /* This has already been cloned */ 753 new_node_id = cur_entry->id; 754 } 755 else 756 { 757 node_revision_t *noderev; 758 759 /* Go get a fresh NODE-REVISION for current child node. */ 760 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, cur_entry->id, 761 trail, pool)); 762 763 /* Do the clone thingy here. */ 764 noderev->predecessor_id = cur_entry->id; 765 if (noderev->predecessor_count != -1) 766 noderev->predecessor_count++; 767 noderev->created_path = svn_fspath__join(parent_path, name, pool); 768 SVN_ERR(svn_fs_base__create_successor(&new_node_id, fs, cur_entry->id, 769 noderev, copy_id, txn_id, 770 trail, pool)); 771 772 /* Replace the ID in the parent's ENTRY list with the ID which 773 refers to the mutable clone of this child. */ 774 SVN_ERR(set_entry(parent, name, new_node_id, txn_id, trail, pool)); 775 } 776 777 /* Initialize the youngster. */ 778 return svn_fs_base__dag_get_node(child_p, fs, new_node_id, trail, pool); 779} 780 781 782 783svn_error_t * 784svn_fs_base__dag_clone_root(dag_node_t **root_p, 785 svn_fs_t *fs, 786 const char *txn_id, 787 trail_t *trail, 788 apr_pool_t *pool) 789{ 790 const svn_fs_id_t *base_root_id, *root_id; 791 node_revision_t *noderev; 792 793 /* Get the node ID's of the root directories of the transaction and 794 its base revision. */ 795 SVN_ERR(svn_fs_base__get_txn_ids(&root_id, &base_root_id, fs, txn_id, 796 trail, pool)); 797 798 /* Oh, give me a clone... 799 (If they're the same, we haven't cloned the transaction's root 800 directory yet.) */ 801 if (svn_fs_base__id_eq(root_id, base_root_id)) 802 { 803 const char *base_copy_id = svn_fs_base__id_copy_id(base_root_id); 804 805 /* Of my own flesh and bone... 806 (Get the NODE-REVISION for the base node, and then write 807 it back out as the clone.) */ 808 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, base_root_id, 809 trail, pool)); 810 811 /* With its Y-chromosome changed to X... 812 (Store it with an updated predecessor count.) */ 813 /* ### TODO: Does it even makes sense to have a different copy id for 814 the root node? That is, does this function need a copy_id 815 passed in? */ 816 noderev->predecessor_id = svn_fs_base__id_copy(base_root_id, pool); 817 if (noderev->predecessor_count != -1) 818 noderev->predecessor_count++; 819 SVN_ERR(svn_fs_base__create_successor(&root_id, fs, base_root_id, 820 noderev, base_copy_id, 821 txn_id, trail, pool)); 822 823 /* ... And when it is grown 824 * Then my own little clone 825 * Will be of the opposite sex! 826 */ 827 SVN_ERR(svn_fs_base__set_txn_root(fs, txn_id, root_id, trail, pool)); 828 } 829 830 /* 831 * (Sung to the tune of "Home, Home on the Range", with thanks to 832 * Randall Garrett and Isaac Asimov.) 833 */ 834 835 /* One way or another, root_id now identifies a cloned root node. */ 836 return svn_fs_base__dag_get_node(root_p, fs, root_id, trail, pool); 837} 838 839 840svn_error_t * 841svn_fs_base__dag_delete(dag_node_t *parent, 842 const char *name, 843 const char *txn_id, 844 trail_t *trail, 845 apr_pool_t *pool) 846{ 847 node_revision_t *parent_noderev; 848 const char *rep_key, *mutable_rep_key; 849 apr_hash_t *entries = NULL; 850 svn_skel_t *entries_skel; 851 svn_fs_t *fs = parent->fs; 852 svn_string_t str; 853 svn_fs_id_t *id = NULL; 854 dag_node_t *node; 855 856 /* Make sure parent is a directory. */ 857 if (parent->kind != svn_node_dir) 858 return svn_error_createf 859 (SVN_ERR_FS_NOT_DIRECTORY, NULL, 860 _("Attempted to delete entry '%s' from *non*-directory node"), name); 861 862 /* Make sure parent is mutable. */ 863 if (! svn_fs_base__dag_check_mutable(parent, txn_id)) 864 return svn_error_createf 865 (SVN_ERR_FS_NOT_MUTABLE, NULL, 866 _("Attempted to delete entry '%s' from immutable directory node"), 867 name); 868 869 /* Make sure that NAME is a single path component. */ 870 if (! svn_path_is_single_path_component(name)) 871 return svn_error_createf 872 (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL, 873 _("Attempted to delete a node with an illegal name '%s'"), name); 874 875 /* Get a fresh NODE-REVISION for the parent node. */ 876 SVN_ERR(svn_fs_bdb__get_node_revision(&parent_noderev, fs, parent->id, 877 trail, pool)); 878 879 /* Get the key for the parent's entries list (data) representation. */ 880 rep_key = parent_noderev->data_key; 881 882 /* No REP_KEY means no representation, and no representation means 883 no data, and no data means no entries...there's nothing here to 884 delete! */ 885 if (! rep_key) 886 return svn_error_createf 887 (SVN_ERR_FS_NO_SUCH_ENTRY, NULL, 888 _("Delete failed: directory has no entry '%s'"), name); 889 890 /* Ensure we have a key to a mutable representation of the entries 891 list. We'll have to update the NODE-REVISION if it points to an 892 immutable version. */ 893 SVN_ERR(svn_fs_base__get_mutable_rep(&mutable_rep_key, rep_key, 894 fs, txn_id, trail, pool)); 895 if (! svn_fs_base__same_keys(mutable_rep_key, rep_key)) 896 { 897 parent_noderev->data_key = mutable_rep_key; 898 SVN_ERR(svn_fs_bdb__put_node_revision(fs, parent->id, parent_noderev, 899 trail, pool)); 900 } 901 902 /* Read the representation, then use it to get the string that holds 903 the entries list. Parse that list into a skel, and parse *that* 904 into a hash. */ 905 906 SVN_ERR(svn_fs_base__rep_contents(&str, fs, rep_key, trail, pool)); 907 entries_skel = svn_skel__parse(str.data, str.len, pool); 908 if (entries_skel) 909 SVN_ERR(svn_fs_base__parse_entries_skel(&entries, entries_skel, pool)); 910 911 /* Find NAME in the ENTRIES skel. */ 912 if (entries) 913 id = svn_hash_gets(entries, name); 914 915 /* If we never found ID in ENTRIES (perhaps because there are no 916 ENTRIES, perhaps because ID just isn't in the existing ENTRIES 917 ... it doesn't matter), return an error. */ 918 if (! id) 919 return svn_error_createf 920 (SVN_ERR_FS_NO_SUCH_ENTRY, NULL, 921 _("Delete failed: directory has no entry '%s'"), name); 922 923 /* Use the ID of this ENTRY to get the entry's node. */ 924 SVN_ERR(svn_fs_base__dag_get_node(&node, svn_fs_base__dag_get_fs(parent), 925 id, trail, pool)); 926 927 /* If mutable, remove it and any mutable children from db. */ 928 SVN_ERR(svn_fs_base__dag_delete_if_mutable(parent->fs, id, txn_id, 929 trail, pool)); 930 931 /* Remove this entry from its parent's entries list. */ 932 svn_hash_sets(entries, name, NULL); 933 934 /* Replace the old entries list with the new one. */ 935 { 936 svn_stream_t *ws; 937 svn_stringbuf_t *unparsed_entries; 938 apr_size_t len; 939 940 SVN_ERR(svn_fs_base__unparse_entries_skel(&entries_skel, entries, pool)); 941 unparsed_entries = svn_skel__unparse(entries_skel, pool); 942 SVN_ERR(svn_fs_base__rep_contents_write_stream(&ws, fs, mutable_rep_key, 943 txn_id, TRUE, trail, 944 pool)); 945 len = unparsed_entries->len; 946 SVN_ERR(svn_stream_write(ws, unparsed_entries->data, &len)); 947 SVN_ERR(svn_stream_close(ws)); 948 } 949 950 return SVN_NO_ERROR; 951} 952 953 954svn_error_t * 955svn_fs_base__dag_remove_node(svn_fs_t *fs, 956 const svn_fs_id_t *id, 957 const char *txn_id, 958 trail_t *trail, 959 apr_pool_t *pool) 960{ 961 dag_node_t *node; 962 node_revision_t *noderev; 963 964 /* Fetch the node. */ 965 SVN_ERR(svn_fs_base__dag_get_node(&node, fs, id, trail, pool)); 966 967 /* If immutable, do nothing and return immediately. */ 968 if (! svn_fs_base__dag_check_mutable(node, txn_id)) 969 return svn_error_createf(SVN_ERR_FS_NOT_MUTABLE, NULL, 970 _("Attempted removal of immutable node")); 971 972 /* Get a fresh node-revision. */ 973 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, id, trail, pool)); 974 975 /* Delete any mutable property representation. */ 976 if (noderev->prop_key) 977 SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->prop_key, 978 txn_id, trail, pool)); 979 980 /* Delete any mutable data representation. */ 981 if (noderev->data_key) 982 SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->data_key, 983 txn_id, trail, pool)); 984 985 /* Delete any mutable edit representation (files only). */ 986 if (noderev->edit_key) 987 SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->edit_key, 988 txn_id, trail, pool)); 989 990 /* Delete the node revision itself. */ 991 return svn_fs_base__delete_node_revision(fs, id, 992 noderev->predecessor_id == NULL, 993 trail, pool); 994} 995 996 997svn_error_t * 998svn_fs_base__dag_delete_if_mutable(svn_fs_t *fs, 999 const svn_fs_id_t *id, 1000 const char *txn_id, 1001 trail_t *trail, 1002 apr_pool_t *pool) 1003{ 1004 dag_node_t *node; 1005 1006 /* Get the node. */ 1007 SVN_ERR(svn_fs_base__dag_get_node(&node, fs, id, trail, pool)); 1008 1009 /* If immutable, do nothing and return immediately. */ 1010 if (! svn_fs_base__dag_check_mutable(node, txn_id)) 1011 return SVN_NO_ERROR; 1012 1013 /* Else it's mutable. Recurse on directories... */ 1014 if (node->kind == svn_node_dir) 1015 { 1016 apr_hash_t *entries; 1017 apr_hash_index_t *hi; 1018 1019 /* Loop over hash entries */ 1020 SVN_ERR(svn_fs_base__dag_dir_entries(&entries, node, trail, pool)); 1021 if (entries) 1022 { 1023 apr_pool_t *subpool = svn_pool_create(pool); 1024 for (hi = apr_hash_first(pool, entries); 1025 hi; 1026 hi = apr_hash_next(hi)) 1027 { 1028 void *val; 1029 svn_fs_dirent_t *dirent; 1030 1031 svn_pool_clear(subpool); 1032 apr_hash_this(hi, NULL, NULL, &val); 1033 dirent = val; 1034 SVN_ERR(svn_fs_base__dag_delete_if_mutable(fs, dirent->id, 1035 txn_id, trail, 1036 subpool)); 1037 } 1038 svn_pool_destroy(subpool); 1039 } 1040 } 1041 1042 /* ... then delete the node itself, any mutable representations and 1043 strings it points to, and possibly its node-origins record. */ 1044 return svn_fs_base__dag_remove_node(fs, id, txn_id, trail, pool); 1045} 1046 1047 1048svn_error_t * 1049svn_fs_base__dag_make_file(dag_node_t **child_p, 1050 dag_node_t *parent, 1051 const char *parent_path, 1052 const char *name, 1053 const char *txn_id, 1054 trail_t *trail, 1055 apr_pool_t *pool) 1056{ 1057 /* Call our little helper function */ 1058 return make_entry(child_p, parent, parent_path, name, FALSE, 1059 txn_id, trail, pool); 1060} 1061 1062 1063svn_error_t * 1064svn_fs_base__dag_make_dir(dag_node_t **child_p, 1065 dag_node_t *parent, 1066 const char *parent_path, 1067 const char *name, 1068 const char *txn_id, 1069 trail_t *trail, 1070 apr_pool_t *pool) 1071{ 1072 /* Call our little helper function */ 1073 return make_entry(child_p, parent, parent_path, name, TRUE, 1074 txn_id, trail, pool); 1075} 1076 1077 1078svn_error_t * 1079svn_fs_base__dag_get_contents(svn_stream_t **contents, 1080 dag_node_t *file, 1081 trail_t *trail, 1082 apr_pool_t *pool) 1083{ 1084 node_revision_t *noderev; 1085 1086 /* Make sure our node is a file. */ 1087 if (file->kind != svn_node_file) 1088 return svn_error_createf 1089 (SVN_ERR_FS_NOT_FILE, NULL, 1090 _("Attempted to get textual contents of a *non*-file node")); 1091 1092 /* Go get a fresh node-revision for FILE. */ 1093 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, file->fs, file->id, 1094 trail, pool)); 1095 1096 /* Our job is to _return_ a stream on the file's contents, so the 1097 stream has to be trail-independent. Here, we pass NULL to tell 1098 the stream that we're not providing it a trail that lives across 1099 reads. This means the stream will do each read in a one-off, 1100 temporary trail. */ 1101 return svn_fs_base__rep_contents_read_stream(contents, file->fs, 1102 noderev->data_key, 1103 FALSE, trail, pool); 1104 1105 /* Note that we're not registering any `close' func, because there's 1106 nothing to cleanup outside of our trail. When the trail is 1107 freed, the stream/baton will be too. */ 1108} 1109 1110 1111svn_error_t * 1112svn_fs_base__dag_file_length(svn_filesize_t *length, 1113 dag_node_t *file, 1114 trail_t *trail, 1115 apr_pool_t *pool) 1116{ 1117 node_revision_t *noderev; 1118 1119 /* Make sure our node is a file. */ 1120 if (file->kind != svn_node_file) 1121 return svn_error_createf 1122 (SVN_ERR_FS_NOT_FILE, NULL, 1123 _("Attempted to get length of a *non*-file node")); 1124 1125 /* Go get a fresh node-revision for FILE, and . */ 1126 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, file->fs, file->id, 1127 trail, pool)); 1128 if (noderev->data_key) 1129 SVN_ERR(svn_fs_base__rep_contents_size(length, file->fs, 1130 noderev->data_key, trail, pool)); 1131 else 1132 *length = 0; 1133 1134 return SVN_NO_ERROR; 1135} 1136 1137 1138svn_error_t * 1139svn_fs_base__dag_file_checksum(svn_checksum_t **checksum, 1140 svn_checksum_kind_t checksum_kind, 1141 dag_node_t *file, 1142 trail_t *trail, 1143 apr_pool_t *pool) 1144{ 1145 node_revision_t *noderev; 1146 1147 if (file->kind != svn_node_file) 1148 return svn_error_createf 1149 (SVN_ERR_FS_NOT_FILE, NULL, 1150 _("Attempted to get checksum of a *non*-file node")); 1151 1152 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, file->fs, file->id, 1153 trail, pool)); 1154 if (! noderev->data_key) 1155 { 1156 *checksum = NULL; 1157 return SVN_NO_ERROR; 1158 } 1159 1160 if (checksum_kind == svn_checksum_md5) 1161 return svn_fs_base__rep_contents_checksums(checksum, NULL, file->fs, 1162 noderev->data_key, 1163 trail, pool); 1164 else if (checksum_kind == svn_checksum_sha1) 1165 return svn_fs_base__rep_contents_checksums(NULL, checksum, file->fs, 1166 noderev->data_key, 1167 trail, pool); 1168 else 1169 return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, NULL); 1170} 1171 1172 1173svn_error_t * 1174svn_fs_base__dag_get_edit_stream(svn_stream_t **contents, 1175 dag_node_t *file, 1176 const char *txn_id, 1177 trail_t *trail, 1178 apr_pool_t *pool) 1179{ 1180 svn_fs_t *fs = file->fs; /* just for nicer indentation */ 1181 node_revision_t *noderev; 1182 const char *mutable_rep_key; 1183 svn_stream_t *ws; 1184 1185 /* Make sure our node is a file. */ 1186 if (file->kind != svn_node_file) 1187 return svn_error_createf 1188 (SVN_ERR_FS_NOT_FILE, NULL, 1189 _("Attempted to set textual contents of a *non*-file node")); 1190 1191 /* Make sure our node is mutable. */ 1192 if (! svn_fs_base__dag_check_mutable(file, txn_id)) 1193 return svn_error_createf 1194 (SVN_ERR_FS_NOT_MUTABLE, NULL, 1195 _("Attempted to set textual contents of an immutable node")); 1196 1197 /* Get the node revision. */ 1198 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, file->id, 1199 trail, pool)); 1200 1201 /* If this node already has an EDIT-DATA-KEY, destroy the data 1202 associated with that key. */ 1203 if (noderev->edit_key) 1204 SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->edit_key, 1205 txn_id, trail, pool)); 1206 1207 /* Now, let's ensure that we have a new EDIT-DATA-KEY available for 1208 use. */ 1209 SVN_ERR(svn_fs_base__get_mutable_rep(&mutable_rep_key, NULL, fs, 1210 txn_id, trail, pool)); 1211 1212 /* We made a new rep, so update the node revision. */ 1213 noderev->edit_key = mutable_rep_key; 1214 SVN_ERR(svn_fs_bdb__put_node_revision(fs, file->id, noderev, 1215 trail, pool)); 1216 1217 /* Return a writable stream with which to set new contents. */ 1218 SVN_ERR(svn_fs_base__rep_contents_write_stream(&ws, fs, mutable_rep_key, 1219 txn_id, FALSE, trail, 1220 pool)); 1221 *contents = ws; 1222 1223 return SVN_NO_ERROR; 1224} 1225 1226 1227 1228svn_error_t * 1229svn_fs_base__dag_finalize_edits(dag_node_t *file, 1230 const svn_checksum_t *checksum, 1231 const char *txn_id, 1232 trail_t *trail, 1233 apr_pool_t *pool) 1234{ 1235 svn_fs_t *fs = file->fs; /* just for nicer indentation */ 1236 node_revision_t *noderev; 1237 const char *old_data_key, *new_data_key, *useless_data_key = NULL; 1238 const char *data_key_uniquifier = NULL; 1239 svn_checksum_t *md5_checksum, *sha1_checksum; 1240 base_fs_data_t *bfd = fs->fsap_data; 1241 1242 /* Make sure our node is a file. */ 1243 if (file->kind != svn_node_file) 1244 return svn_error_createf 1245 (SVN_ERR_FS_NOT_FILE, NULL, 1246 _("Attempted to set textual contents of a *non*-file node")); 1247 1248 /* Make sure our node is mutable. */ 1249 if (! svn_fs_base__dag_check_mutable(file, txn_id)) 1250 return svn_error_createf 1251 (SVN_ERR_FS_NOT_MUTABLE, NULL, 1252 _("Attempted to set textual contents of an immutable node")); 1253 1254 /* Get the node revision. */ 1255 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, file->id, 1256 trail, pool)); 1257 1258 /* If this node has no EDIT-DATA-KEY, this is a no-op. */ 1259 if (! noderev->edit_key) 1260 return SVN_NO_ERROR; 1261 1262 /* Get our representation's checksums. */ 1263 SVN_ERR(svn_fs_base__rep_contents_checksums(&md5_checksum, &sha1_checksum, 1264 fs, noderev->edit_key, 1265 trail, pool)); 1266 1267 /* If our caller provided a checksum of the right kind to compare, do so. */ 1268 if (checksum) 1269 { 1270 svn_checksum_t *test_checksum; 1271 1272 if (checksum->kind == svn_checksum_md5) 1273 test_checksum = md5_checksum; 1274 else if (checksum->kind == svn_checksum_sha1) 1275 test_checksum = sha1_checksum; 1276 else 1277 return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, NULL); 1278 1279 if (! svn_checksum_match(checksum, test_checksum)) 1280 return svn_checksum_mismatch_err(checksum, test_checksum, pool, 1281 _("Checksum mismatch on representation '%s'"), 1282 noderev->edit_key); 1283 } 1284 1285 /* Now, we want to delete the old representation and replace it with 1286 the new. Of course, we don't actually delete anything until 1287 everything is being properly referred to by the node-revision 1288 skel. 1289 1290 Now, if the result of all this editing is that we've created a 1291 representation that describes content already represented 1292 immutably in our database, we don't even need to keep these edits. 1293 We can simply point our data_key at that pre-existing 1294 representation and throw away our work! In this situation, 1295 though, we'll need a unique ID to help other code distinguish 1296 between "the contents weren't touched" and "the contents were 1297 touched but still look the same" (to state it oversimply). */ 1298 old_data_key = noderev->data_key; 1299 if (sha1_checksum && bfd->format >= SVN_FS_BASE__MIN_REP_SHARING_FORMAT) 1300 { 1301 svn_error_t *err = svn_fs_bdb__get_checksum_rep(&new_data_key, fs, 1302 sha1_checksum, 1303 trail, pool); 1304 if (! err) 1305 { 1306 useless_data_key = noderev->edit_key; 1307 err = svn_fs_bdb__reserve_rep_reuse_id(&data_key_uniquifier, 1308 trail->fs, trail, pool); 1309 } 1310 else if (err && (err->apr_err == SVN_ERR_FS_NO_SUCH_CHECKSUM_REP)) 1311 { 1312 svn_error_clear(err); 1313 err = SVN_NO_ERROR; 1314 new_data_key = noderev->edit_key; 1315 } 1316 SVN_ERR(err); 1317 } 1318 else 1319 { 1320 new_data_key = noderev->edit_key; 1321 } 1322 1323 noderev->data_key = new_data_key; 1324 noderev->data_key_uniquifier = data_key_uniquifier; 1325 noderev->edit_key = NULL; 1326 1327 SVN_ERR(svn_fs_bdb__put_node_revision(fs, file->id, noderev, trail, pool)); 1328 1329 /* Only *now* can we safely destroy the old representation (if it 1330 even existed in the first place). */ 1331 if (old_data_key) 1332 SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, old_data_key, txn_id, 1333 trail, pool)); 1334 1335 /* If we've got a discardable rep (probably because we ended up 1336 re-using a preexisting one), throw out the discardable rep. */ 1337 if (useless_data_key) 1338 SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, useless_data_key, 1339 txn_id, trail, pool)); 1340 1341 return SVN_NO_ERROR; 1342} 1343 1344 1345 1346dag_node_t * 1347svn_fs_base__dag_dup(const dag_node_t *node, 1348 apr_pool_t *pool) 1349{ 1350 /* Allocate our new node. */ 1351 dag_node_t *new_node = apr_pcalloc(pool, sizeof(*new_node)); 1352 1353 new_node->fs = node->fs; 1354 new_node->id = svn_fs_base__id_copy(node->id, pool); 1355 new_node->kind = node->kind; 1356 new_node->created_path = apr_pstrdup(pool, node->created_path); 1357 return new_node; 1358} 1359 1360 1361svn_error_t * 1362svn_fs_base__dag_open(dag_node_t **child_p, 1363 dag_node_t *parent, 1364 const char *name, 1365 trail_t *trail, 1366 apr_pool_t *pool) 1367{ 1368 const svn_fs_id_t *node_id; 1369 1370 /* Ensure that NAME exists in PARENT's entry list. */ 1371 SVN_ERR(dir_entry_id_from_node(&node_id, parent, name, trail, pool)); 1372 if (! node_id) 1373 return svn_error_createf 1374 (SVN_ERR_FS_NOT_FOUND, NULL, 1375 _("Attempted to open non-existent child node '%s'"), name); 1376 1377 /* Make sure that NAME is a single path component. */ 1378 if (! svn_path_is_single_path_component(name)) 1379 return svn_error_createf 1380 (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL, 1381 _("Attempted to open node with an illegal name '%s'"), name); 1382 1383 /* Now get the node that was requested. */ 1384 return svn_fs_base__dag_get_node(child_p, svn_fs_base__dag_get_fs(parent), 1385 node_id, trail, pool); 1386} 1387 1388 1389svn_error_t * 1390svn_fs_base__dag_copy(dag_node_t *to_node, 1391 const char *entry, 1392 dag_node_t *from_node, 1393 svn_boolean_t preserve_history, 1394 svn_revnum_t from_rev, 1395 const char *from_path, 1396 const char *txn_id, 1397 trail_t *trail, 1398 apr_pool_t *pool) 1399{ 1400 const svn_fs_id_t *id; 1401 1402 if (preserve_history) 1403 { 1404 node_revision_t *noderev; 1405 const char *copy_id; 1406 svn_fs_t *fs = svn_fs_base__dag_get_fs(from_node); 1407 const svn_fs_id_t *src_id = svn_fs_base__dag_get_id(from_node); 1408 const char *from_txn_id = NULL; 1409 1410 /* Make a copy of the original node revision. */ 1411 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, from_node->id, 1412 trail, pool)); 1413 1414 /* Reserve a copy ID for this new copy. */ 1415 SVN_ERR(svn_fs_bdb__reserve_copy_id(©_id, fs, trail, pool)); 1416 1417 /* Create a successor with its predecessor pointing at the copy 1418 source. */ 1419 noderev->predecessor_id = svn_fs_base__id_copy(src_id, pool); 1420 if (noderev->predecessor_count != -1) 1421 noderev->predecessor_count++; 1422 noderev->created_path = svn_fspath__join 1423 (svn_fs_base__dag_get_created_path(to_node), entry, pool); 1424 SVN_ERR(svn_fs_base__create_successor(&id, fs, src_id, noderev, 1425 copy_id, txn_id, trail, pool)); 1426 1427 /* Translate FROM_REV into a transaction ID. */ 1428 SVN_ERR(svn_fs_base__rev_get_txn_id(&from_txn_id, fs, from_rev, 1429 trail, pool)); 1430 1431 /* Now that we've done the copy, we need to add the information 1432 about the copy to the `copies' table, using the COPY_ID we 1433 reserved above. */ 1434 SVN_ERR(svn_fs_bdb__create_copy 1435 (fs, copy_id, 1436 svn_fs__canonicalize_abspath(from_path, pool), 1437 from_txn_id, id, copy_kind_real, trail, pool)); 1438 1439 /* Finally, add the COPY_ID to the transaction's list of copies 1440 so that, if this transaction is aborted, the `copies' table 1441 entry we added above will be cleaned up. */ 1442 SVN_ERR(svn_fs_base__add_txn_copy(fs, txn_id, copy_id, trail, pool)); 1443 } 1444 else /* don't preserve history */ 1445 { 1446 id = svn_fs_base__dag_get_id(from_node); 1447 } 1448 1449 /* Set the entry in to_node to the new id. */ 1450 return svn_fs_base__dag_set_entry(to_node, entry, id, txn_id, 1451 trail, pool); 1452} 1453 1454 1455 1456/*** Deltification ***/ 1457 1458/* Maybe change the representation identified by TARGET_REP_KEY to be 1459 a delta against the representation identified by SOURCE_REP_KEY. 1460 Some reasons why we wouldn't include: 1461 1462 - TARGET_REP_KEY and SOURCE_REP_KEY are the same key. 1463 1464 - TARGET_REP_KEY's representation isn't mutable in TXN_ID (if 1465 TXN_ID is non-NULL). 1466 1467 - The delta provides less space savings that a fulltext (this is 1468 a detail handled by lower logic layers, not this function). 1469 1470 Do this work in TRAIL, using POOL for necessary allocations. 1471*/ 1472static svn_error_t * 1473maybe_deltify_mutable_rep(const char *target_rep_key, 1474 const char *source_rep_key, 1475 const char *txn_id, 1476 trail_t *trail, 1477 apr_pool_t *pool) 1478{ 1479 if (! (target_rep_key && source_rep_key 1480 && (strcmp(target_rep_key, source_rep_key) != 0))) 1481 return SVN_NO_ERROR; 1482 1483 if (txn_id) 1484 { 1485 representation_t *target_rep; 1486 SVN_ERR(svn_fs_bdb__read_rep(&target_rep, trail->fs, target_rep_key, 1487 trail, pool)); 1488 if (strcmp(target_rep->txn_id, txn_id) != 0) 1489 return SVN_NO_ERROR; 1490 } 1491 1492 return svn_fs_base__rep_deltify(trail->fs, target_rep_key, source_rep_key, 1493 trail, pool); 1494} 1495 1496 1497svn_error_t * 1498svn_fs_base__dag_deltify(dag_node_t *target, 1499 dag_node_t *source, 1500 svn_boolean_t props_only, 1501 const char *txn_id, 1502 trail_t *trail, 1503 apr_pool_t *pool) 1504{ 1505 node_revision_t *source_nr, *target_nr; 1506 svn_fs_t *fs = svn_fs_base__dag_get_fs(target); 1507 1508 /* Get node revisions for the two nodes. */ 1509 SVN_ERR(svn_fs_bdb__get_node_revision(&target_nr, fs, target->id, 1510 trail, pool)); 1511 SVN_ERR(svn_fs_bdb__get_node_revision(&source_nr, fs, source->id, 1512 trail, pool)); 1513 1514 /* If TARGET and SOURCE both have properties, and are not sharing a 1515 property key, deltify TARGET's properties. */ 1516 SVN_ERR(maybe_deltify_mutable_rep(target_nr->prop_key, source_nr->prop_key, 1517 txn_id, trail, pool)); 1518 1519 /* If we are not only attending to properties, and if TARGET and 1520 SOURCE both have data, and are not sharing a data key, deltify 1521 TARGET's data. */ 1522 if (! props_only) 1523 SVN_ERR(maybe_deltify_mutable_rep(target_nr->data_key, source_nr->data_key, 1524 txn_id, trail, pool)); 1525 1526 return SVN_NO_ERROR; 1527} 1528 1529/* Maybe store a `checksum-reps' index record for the representation whose 1530 key is REP. (If there's already a rep for this checksum, we don't 1531 bother overwriting it.) */ 1532static svn_error_t * 1533maybe_store_checksum_rep(const char *rep, 1534 trail_t *trail, 1535 apr_pool_t *pool) 1536{ 1537 svn_error_t *err = SVN_NO_ERROR; 1538 svn_fs_t *fs = trail->fs; 1539 svn_checksum_t *sha1_checksum; 1540 1541 /* We want the SHA1 checksum, if any. */ 1542 SVN_ERR(svn_fs_base__rep_contents_checksums(NULL, &sha1_checksum, 1543 fs, rep, trail, pool)); 1544 if (sha1_checksum) 1545 { 1546 err = svn_fs_bdb__set_checksum_rep(fs, sha1_checksum, rep, trail, pool); 1547 if (err && (err->apr_err == SVN_ERR_FS_ALREADY_EXISTS)) 1548 { 1549 svn_error_clear(err); 1550 err = SVN_NO_ERROR; 1551 } 1552 } 1553 return svn_error_trace(err); 1554} 1555 1556svn_error_t * 1557svn_fs_base__dag_index_checksums(dag_node_t *node, 1558 trail_t *trail, 1559 apr_pool_t *pool) 1560{ 1561 node_revision_t *node_rev; 1562 1563 SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, trail->fs, node->id, 1564 trail, pool)); 1565 if ((node_rev->kind == svn_node_file) && node_rev->data_key) 1566 SVN_ERR(maybe_store_checksum_rep(node_rev->data_key, trail, pool)); 1567 if (node_rev->prop_key) 1568 SVN_ERR(maybe_store_checksum_rep(node_rev->prop_key, trail, pool)); 1569 1570 return SVN_NO_ERROR; 1571} 1572 1573 1574 1575/*** Committing ***/ 1576 1577svn_error_t * 1578svn_fs_base__dag_commit_txn(svn_revnum_t *new_rev, 1579 svn_fs_txn_t *txn, 1580 trail_t *trail, 1581 apr_pool_t *pool) 1582{ 1583 revision_t revision; 1584 apr_hash_t *txnprops; 1585 svn_fs_t *fs = txn->fs; 1586 const char *txn_id = txn->id; 1587 const svn_string_t *client_date; 1588 1589 /* Remove any temporary transaction properties initially created by 1590 begin_txn(). */ 1591 SVN_ERR(svn_fs_base__txn_proplist_in_trail(&txnprops, txn_id, trail)); 1592 1593 /* Add new revision entry to `revisions' table. */ 1594 revision.txn_id = txn_id; 1595 *new_rev = SVN_INVALID_REVNUM; 1596 SVN_ERR(svn_fs_bdb__put_rev(new_rev, fs, &revision, trail, pool)); 1597 1598 if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD)) 1599 SVN_ERR(svn_fs_base__set_txn_prop 1600 (fs, txn_id, SVN_FS__PROP_TXN_CHECK_OOD, NULL, trail, pool)); 1601 1602 if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS)) 1603 SVN_ERR(svn_fs_base__set_txn_prop 1604 (fs, txn_id, SVN_FS__PROP_TXN_CHECK_LOCKS, NULL, trail, pool)); 1605 1606 client_date = svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CLIENT_DATE); 1607 if (client_date) 1608 SVN_ERR(svn_fs_base__set_txn_prop 1609 (fs, txn_id, SVN_FS__PROP_TXN_CLIENT_DATE, NULL, trail, pool)); 1610 1611 /* Promote the unfinished transaction to a committed one. */ 1612 SVN_ERR(svn_fs_base__txn_make_committed(fs, txn_id, *new_rev, 1613 trail, pool)); 1614 1615 if (!client_date || strcmp(client_date->data, "1")) 1616 { 1617 /* Set a date on the commit if requested. We wait until now to fetch the 1618 date, so it's definitely newer than any previous revision's date. */ 1619 svn_string_t date; 1620 date.data = svn_time_to_cstring(apr_time_now(), pool); 1621 date.len = strlen(date.data); 1622 SVN_ERR(svn_fs_base__set_rev_prop(fs, *new_rev, SVN_PROP_REVISION_DATE, 1623 NULL, &date, trail, pool)); 1624 } 1625 1626 return SVN_NO_ERROR; 1627} 1628 1629 1630/*** Comparison. ***/ 1631 1632svn_error_t * 1633svn_fs_base__things_different(svn_boolean_t *props_changed, 1634 svn_boolean_t *contents_changed, 1635 dag_node_t *node1, 1636 dag_node_t *node2, 1637 trail_t *trail, 1638 apr_pool_t *pool) 1639{ 1640 node_revision_t *noderev1, *noderev2; 1641 1642 /* If we have no place to store our results, don't bother doing 1643 anything. */ 1644 if (! props_changed && ! contents_changed) 1645 return SVN_NO_ERROR; 1646 1647 /* The node revision skels for these two nodes. */ 1648 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev1, node1->fs, node1->id, 1649 trail, pool)); 1650 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev2, node2->fs, node2->id, 1651 trail, pool)); 1652 1653 /* Compare property keys. */ 1654 if (props_changed != NULL) 1655 *props_changed = (! svn_fs_base__same_keys(noderev1->prop_key, 1656 noderev2->prop_key)); 1657 1658 /* Compare contents keys and their (optional) uniquifiers. */ 1659 if (contents_changed != NULL) 1660 *contents_changed = 1661 (! (svn_fs_base__same_keys(noderev1->data_key, 1662 noderev2->data_key) 1663 /* Technically, these uniquifiers aren't used and "keys", 1664 but keys are base-36 stringified numbers, so we'll take 1665 this liberty. */ 1666 && (svn_fs_base__same_keys(noderev1->data_key_uniquifier, 1667 noderev2->data_key_uniquifier)))); 1668 1669 return SVN_NO_ERROR; 1670} 1671 1672 1673 1674/*** Mergeinfo tracking stuff ***/ 1675 1676svn_error_t * 1677svn_fs_base__dag_get_mergeinfo_stats(svn_boolean_t *has_mergeinfo, 1678 apr_int64_t *count, 1679 dag_node_t *node, 1680 trail_t *trail, 1681 apr_pool_t *pool) 1682{ 1683 node_revision_t *node_rev; 1684 svn_fs_t *fs = svn_fs_base__dag_get_fs(node); 1685 const svn_fs_id_t *id = svn_fs_base__dag_get_id(node); 1686 1687 SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, fs, id, trail, pool)); 1688 if (has_mergeinfo) 1689 *has_mergeinfo = node_rev->has_mergeinfo; 1690 if (count) 1691 *count = node_rev->mergeinfo_count; 1692 return SVN_NO_ERROR; 1693} 1694 1695 1696svn_error_t * 1697svn_fs_base__dag_set_has_mergeinfo(dag_node_t *node, 1698 svn_boolean_t has_mergeinfo, 1699 svn_boolean_t *had_mergeinfo, 1700 const char *txn_id, 1701 trail_t *trail, 1702 apr_pool_t *pool) 1703{ 1704 node_revision_t *node_rev; 1705 svn_fs_t *fs = svn_fs_base__dag_get_fs(node); 1706 const svn_fs_id_t *id = svn_fs_base__dag_get_id(node); 1707 1708 SVN_ERR(svn_fs_base__test_required_feature_format 1709 (trail->fs, "mergeinfo", SVN_FS_BASE__MIN_MERGEINFO_FORMAT)); 1710 1711 if (! svn_fs_base__dag_check_mutable(node, txn_id)) 1712 return svn_error_createf(SVN_ERR_FS_NOT_MUTABLE, NULL, 1713 _("Attempted merge tracking info change on " 1714 "immutable node")); 1715 1716 SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, fs, id, trail, pool)); 1717 *had_mergeinfo = node_rev->has_mergeinfo; 1718 1719 /* Are we changing the node? */ 1720 if ((! has_mergeinfo) != (! *had_mergeinfo)) 1721 { 1722 /* Note the new has-mergeinfo state. */ 1723 node_rev->has_mergeinfo = has_mergeinfo; 1724 1725 /* Increment or decrement the mergeinfo count as necessary. */ 1726 if (has_mergeinfo) 1727 node_rev->mergeinfo_count++; 1728 else 1729 node_rev->mergeinfo_count--; 1730 1731 SVN_ERR(svn_fs_bdb__put_node_revision(fs, id, node_rev, trail, pool)); 1732 } 1733 return SVN_NO_ERROR; 1734} 1735 1736 1737svn_error_t * 1738svn_fs_base__dag_adjust_mergeinfo_count(dag_node_t *node, 1739 apr_int64_t count_delta, 1740 const char *txn_id, 1741 trail_t *trail, 1742 apr_pool_t *pool) 1743{ 1744 node_revision_t *node_rev; 1745 svn_fs_t *fs = svn_fs_base__dag_get_fs(node); 1746 const svn_fs_id_t *id = svn_fs_base__dag_get_id(node); 1747 1748 SVN_ERR(svn_fs_base__test_required_feature_format 1749 (trail->fs, "mergeinfo", SVN_FS_BASE__MIN_MERGEINFO_FORMAT)); 1750 1751 if (! svn_fs_base__dag_check_mutable(node, txn_id)) 1752 return svn_error_createf(SVN_ERR_FS_NOT_MUTABLE, NULL, 1753 _("Attempted mergeinfo count change on " 1754 "immutable node")); 1755 1756 if (count_delta == 0) 1757 return SVN_NO_ERROR; 1758 1759 SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, fs, id, trail, pool)); 1760 node_rev->mergeinfo_count = node_rev->mergeinfo_count + count_delta; 1761 if ((node_rev->mergeinfo_count < 0) 1762 || ((node->kind == svn_node_file) && (node_rev->mergeinfo_count > 1))) 1763 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 1764 apr_psprintf(pool, 1765 _("Invalid value (%%%s) for node " 1766 "revision mergeinfo count"), 1767 APR_INT64_T_FMT), 1768 node_rev->mergeinfo_count); 1769 1770 return svn_fs_bdb__put_node_revision(fs, id, node_rev, trail, pool); 1771} 1772