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 apr_hash_this(hi, NULL, NULL, &val); 1032 dirent = val; 1033 SVN_ERR(svn_fs_base__dag_delete_if_mutable(fs, dirent->id, 1034 txn_id, trail, 1035 subpool)); 1036 } 1037 } 1038 } 1039 1040 /* ... then delete the node itself, any mutable representations and 1041 strings it points to, and possibly its node-origins record. */ 1042 return svn_fs_base__dag_remove_node(fs, id, txn_id, trail, pool); 1043} 1044 1045 1046svn_error_t * 1047svn_fs_base__dag_make_file(dag_node_t **child_p, 1048 dag_node_t *parent, 1049 const char *parent_path, 1050 const char *name, 1051 const char *txn_id, 1052 trail_t *trail, 1053 apr_pool_t *pool) 1054{ 1055 /* Call our little helper function */ 1056 return make_entry(child_p, parent, parent_path, name, FALSE, 1057 txn_id, trail, pool); 1058} 1059 1060 1061svn_error_t * 1062svn_fs_base__dag_make_dir(dag_node_t **child_p, 1063 dag_node_t *parent, 1064 const char *parent_path, 1065 const char *name, 1066 const char *txn_id, 1067 trail_t *trail, 1068 apr_pool_t *pool) 1069{ 1070 /* Call our little helper function */ 1071 return make_entry(child_p, parent, parent_path, name, TRUE, 1072 txn_id, trail, pool); 1073} 1074 1075 1076svn_error_t * 1077svn_fs_base__dag_get_contents(svn_stream_t **contents, 1078 dag_node_t *file, 1079 trail_t *trail, 1080 apr_pool_t *pool) 1081{ 1082 node_revision_t *noderev; 1083 1084 /* Make sure our node is a file. */ 1085 if (file->kind != svn_node_file) 1086 return svn_error_createf 1087 (SVN_ERR_FS_NOT_FILE, NULL, 1088 _("Attempted to get textual contents of a *non*-file node")); 1089 1090 /* Go get a fresh node-revision for FILE. */ 1091 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, file->fs, file->id, 1092 trail, pool)); 1093 1094 /* Our job is to _return_ a stream on the file's contents, so the 1095 stream has to be trail-independent. Here, we pass NULL to tell 1096 the stream that we're not providing it a trail that lives across 1097 reads. This means the stream will do each read in a one-off, 1098 temporary trail. */ 1099 return svn_fs_base__rep_contents_read_stream(contents, file->fs, 1100 noderev->data_key, 1101 FALSE, trail, pool); 1102 1103 /* Note that we're not registering any `close' func, because there's 1104 nothing to cleanup outside of our trail. When the trail is 1105 freed, the stream/baton will be too. */ 1106} 1107 1108 1109svn_error_t * 1110svn_fs_base__dag_file_length(svn_filesize_t *length, 1111 dag_node_t *file, 1112 trail_t *trail, 1113 apr_pool_t *pool) 1114{ 1115 node_revision_t *noderev; 1116 1117 /* Make sure our node is a file. */ 1118 if (file->kind != svn_node_file) 1119 return svn_error_createf 1120 (SVN_ERR_FS_NOT_FILE, NULL, 1121 _("Attempted to get length of a *non*-file node")); 1122 1123 /* Go get a fresh node-revision for FILE, and . */ 1124 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, file->fs, file->id, 1125 trail, pool)); 1126 if (noderev->data_key) 1127 SVN_ERR(svn_fs_base__rep_contents_size(length, file->fs, 1128 noderev->data_key, trail, pool)); 1129 else 1130 *length = 0; 1131 1132 return SVN_NO_ERROR; 1133} 1134 1135 1136svn_error_t * 1137svn_fs_base__dag_file_checksum(svn_checksum_t **checksum, 1138 svn_checksum_kind_t checksum_kind, 1139 dag_node_t *file, 1140 trail_t *trail, 1141 apr_pool_t *pool) 1142{ 1143 node_revision_t *noderev; 1144 1145 if (file->kind != svn_node_file) 1146 return svn_error_createf 1147 (SVN_ERR_FS_NOT_FILE, NULL, 1148 _("Attempted to get checksum of a *non*-file node")); 1149 1150 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, file->fs, file->id, 1151 trail, pool)); 1152 if (! noderev->data_key) 1153 { 1154 *checksum = NULL; 1155 return SVN_NO_ERROR; 1156 } 1157 1158 if (checksum_kind == svn_checksum_md5) 1159 return svn_fs_base__rep_contents_checksums(checksum, NULL, file->fs, 1160 noderev->data_key, 1161 trail, pool); 1162 else if (checksum_kind == svn_checksum_sha1) 1163 return svn_fs_base__rep_contents_checksums(NULL, checksum, file->fs, 1164 noderev->data_key, 1165 trail, pool); 1166 else 1167 return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, NULL); 1168} 1169 1170 1171svn_error_t * 1172svn_fs_base__dag_get_edit_stream(svn_stream_t **contents, 1173 dag_node_t *file, 1174 const char *txn_id, 1175 trail_t *trail, 1176 apr_pool_t *pool) 1177{ 1178 svn_fs_t *fs = file->fs; /* just for nicer indentation */ 1179 node_revision_t *noderev; 1180 const char *mutable_rep_key; 1181 svn_stream_t *ws; 1182 1183 /* Make sure our node is a file. */ 1184 if (file->kind != svn_node_file) 1185 return svn_error_createf 1186 (SVN_ERR_FS_NOT_FILE, NULL, 1187 _("Attempted to set textual contents of a *non*-file node")); 1188 1189 /* Make sure our node is mutable. */ 1190 if (! svn_fs_base__dag_check_mutable(file, txn_id)) 1191 return svn_error_createf 1192 (SVN_ERR_FS_NOT_MUTABLE, NULL, 1193 _("Attempted to set textual contents of an immutable node")); 1194 1195 /* Get the node revision. */ 1196 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, file->id, 1197 trail, pool)); 1198 1199 /* If this node already has an EDIT-DATA-KEY, destroy the data 1200 associated with that key. */ 1201 if (noderev->edit_key) 1202 SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->edit_key, 1203 txn_id, trail, pool)); 1204 1205 /* Now, let's ensure that we have a new EDIT-DATA-KEY available for 1206 use. */ 1207 SVN_ERR(svn_fs_base__get_mutable_rep(&mutable_rep_key, NULL, fs, 1208 txn_id, trail, pool)); 1209 1210 /* We made a new rep, so update the node revision. */ 1211 noderev->edit_key = mutable_rep_key; 1212 SVN_ERR(svn_fs_bdb__put_node_revision(fs, file->id, noderev, 1213 trail, pool)); 1214 1215 /* Return a writable stream with which to set new contents. */ 1216 SVN_ERR(svn_fs_base__rep_contents_write_stream(&ws, fs, mutable_rep_key, 1217 txn_id, FALSE, trail, 1218 pool)); 1219 *contents = ws; 1220 1221 return SVN_NO_ERROR; 1222} 1223 1224 1225 1226svn_error_t * 1227svn_fs_base__dag_finalize_edits(dag_node_t *file, 1228 const svn_checksum_t *checksum, 1229 const char *txn_id, 1230 trail_t *trail, 1231 apr_pool_t *pool) 1232{ 1233 svn_fs_t *fs = file->fs; /* just for nicer indentation */ 1234 node_revision_t *noderev; 1235 const char *old_data_key, *new_data_key, *useless_data_key = NULL; 1236 const char *data_key_uniquifier = NULL; 1237 svn_checksum_t *md5_checksum, *sha1_checksum; 1238 base_fs_data_t *bfd = fs->fsap_data; 1239 1240 /* Make sure our node is a file. */ 1241 if (file->kind != svn_node_file) 1242 return svn_error_createf 1243 (SVN_ERR_FS_NOT_FILE, NULL, 1244 _("Attempted to set textual contents of a *non*-file node")); 1245 1246 /* Make sure our node is mutable. */ 1247 if (! svn_fs_base__dag_check_mutable(file, txn_id)) 1248 return svn_error_createf 1249 (SVN_ERR_FS_NOT_MUTABLE, NULL, 1250 _("Attempted to set textual contents of an immutable node")); 1251 1252 /* Get the node revision. */ 1253 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, file->id, 1254 trail, pool)); 1255 1256 /* If this node has no EDIT-DATA-KEY, this is a no-op. */ 1257 if (! noderev->edit_key) 1258 return SVN_NO_ERROR; 1259 1260 /* Get our representation's checksums. */ 1261 SVN_ERR(svn_fs_base__rep_contents_checksums(&md5_checksum, &sha1_checksum, 1262 fs, noderev->edit_key, 1263 trail, pool)); 1264 1265 /* If our caller provided a checksum of the right kind to compare, do so. */ 1266 if (checksum) 1267 { 1268 svn_checksum_t *test_checksum; 1269 1270 if (checksum->kind == svn_checksum_md5) 1271 test_checksum = md5_checksum; 1272 else if (checksum->kind == svn_checksum_sha1) 1273 test_checksum = sha1_checksum; 1274 else 1275 return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, NULL); 1276 1277 if (! svn_checksum_match(checksum, test_checksum)) 1278 return svn_checksum_mismatch_err(checksum, test_checksum, pool, 1279 _("Checksum mismatch on representation '%s'"), 1280 noderev->edit_key); 1281 } 1282 1283 /* Now, we want to delete the old representation and replace it with 1284 the new. Of course, we don't actually delete anything until 1285 everything is being properly referred to by the node-revision 1286 skel. 1287 1288 Now, if the result of all this editing is that we've created a 1289 representation that describes content already represented 1290 immutably in our database, we don't even need to keep these edits. 1291 We can simply point our data_key at that pre-existing 1292 representation and throw away our work! In this situation, 1293 though, we'll need a unique ID to help other code distinguish 1294 between "the contents weren't touched" and "the contents were 1295 touched but still look the same" (to state it oversimply). */ 1296 old_data_key = noderev->data_key; 1297 if (sha1_checksum && bfd->format >= SVN_FS_BASE__MIN_REP_SHARING_FORMAT) 1298 { 1299 svn_error_t *err = svn_fs_bdb__get_checksum_rep(&new_data_key, fs, 1300 sha1_checksum, 1301 trail, pool); 1302 if (! err) 1303 { 1304 useless_data_key = noderev->edit_key; 1305 err = svn_fs_bdb__reserve_rep_reuse_id(&data_key_uniquifier, 1306 trail->fs, trail, pool); 1307 } 1308 else if (err && (err->apr_err == SVN_ERR_FS_NO_SUCH_CHECKSUM_REP)) 1309 { 1310 svn_error_clear(err); 1311 err = SVN_NO_ERROR; 1312 new_data_key = noderev->edit_key; 1313 } 1314 SVN_ERR(err); 1315 } 1316 else 1317 { 1318 new_data_key = noderev->edit_key; 1319 } 1320 1321 noderev->data_key = new_data_key; 1322 noderev->data_key_uniquifier = data_key_uniquifier; 1323 noderev->edit_key = NULL; 1324 1325 SVN_ERR(svn_fs_bdb__put_node_revision(fs, file->id, noderev, trail, pool)); 1326 1327 /* Only *now* can we safely destroy the old representation (if it 1328 even existed in the first place). */ 1329 if (old_data_key) 1330 SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, old_data_key, txn_id, 1331 trail, pool)); 1332 1333 /* If we've got a discardable rep (probably because we ended up 1334 re-using a preexisting one), throw out the discardable rep. */ 1335 if (useless_data_key) 1336 SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, useless_data_key, 1337 txn_id, trail, pool)); 1338 1339 return SVN_NO_ERROR; 1340} 1341 1342 1343 1344dag_node_t * 1345svn_fs_base__dag_dup(dag_node_t *node, 1346 apr_pool_t *pool) 1347{ 1348 /* Allocate our new node. */ 1349 dag_node_t *new_node = apr_pcalloc(pool, sizeof(*new_node)); 1350 1351 new_node->fs = node->fs; 1352 new_node->id = svn_fs_base__id_copy(node->id, pool); 1353 new_node->kind = node->kind; 1354 new_node->created_path = apr_pstrdup(pool, node->created_path); 1355 return new_node; 1356} 1357 1358 1359svn_error_t * 1360svn_fs_base__dag_open(dag_node_t **child_p, 1361 dag_node_t *parent, 1362 const char *name, 1363 trail_t *trail, 1364 apr_pool_t *pool) 1365{ 1366 const svn_fs_id_t *node_id; 1367 1368 /* Ensure that NAME exists in PARENT's entry list. */ 1369 SVN_ERR(dir_entry_id_from_node(&node_id, parent, name, trail, pool)); 1370 if (! node_id) 1371 return svn_error_createf 1372 (SVN_ERR_FS_NOT_FOUND, NULL, 1373 _("Attempted to open non-existent child node '%s'"), name); 1374 1375 /* Make sure that NAME is a single path component. */ 1376 if (! svn_path_is_single_path_component(name)) 1377 return svn_error_createf 1378 (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL, 1379 _("Attempted to open node with an illegal name '%s'"), name); 1380 1381 /* Now get the node that was requested. */ 1382 return svn_fs_base__dag_get_node(child_p, svn_fs_base__dag_get_fs(parent), 1383 node_id, trail, pool); 1384} 1385 1386 1387svn_error_t * 1388svn_fs_base__dag_copy(dag_node_t *to_node, 1389 const char *entry, 1390 dag_node_t *from_node, 1391 svn_boolean_t preserve_history, 1392 svn_revnum_t from_rev, 1393 const char *from_path, 1394 const char *txn_id, 1395 trail_t *trail, 1396 apr_pool_t *pool) 1397{ 1398 const svn_fs_id_t *id; 1399 1400 if (preserve_history) 1401 { 1402 node_revision_t *noderev; 1403 const char *copy_id; 1404 svn_fs_t *fs = svn_fs_base__dag_get_fs(from_node); 1405 const svn_fs_id_t *src_id = svn_fs_base__dag_get_id(from_node); 1406 const char *from_txn_id = NULL; 1407 1408 /* Make a copy of the original node revision. */ 1409 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, from_node->id, 1410 trail, pool)); 1411 1412 /* Reserve a copy ID for this new copy. */ 1413 SVN_ERR(svn_fs_bdb__reserve_copy_id(©_id, fs, trail, pool)); 1414 1415 /* Create a successor with its predecessor pointing at the copy 1416 source. */ 1417 noderev->predecessor_id = svn_fs_base__id_copy(src_id, pool); 1418 if (noderev->predecessor_count != -1) 1419 noderev->predecessor_count++; 1420 noderev->created_path = svn_fspath__join 1421 (svn_fs_base__dag_get_created_path(to_node), entry, pool); 1422 SVN_ERR(svn_fs_base__create_successor(&id, fs, src_id, noderev, 1423 copy_id, txn_id, trail, pool)); 1424 1425 /* Translate FROM_REV into a transaction ID. */ 1426 SVN_ERR(svn_fs_base__rev_get_txn_id(&from_txn_id, fs, from_rev, 1427 trail, pool)); 1428 1429 /* Now that we've done the copy, we need to add the information 1430 about the copy to the `copies' table, using the COPY_ID we 1431 reserved above. */ 1432 SVN_ERR(svn_fs_bdb__create_copy 1433 (fs, copy_id, 1434 svn_fs__canonicalize_abspath(from_path, pool), 1435 from_txn_id, id, copy_kind_real, trail, pool)); 1436 1437 /* Finally, add the COPY_ID to the transaction's list of copies 1438 so that, if this transaction is aborted, the `copies' table 1439 entry we added above will be cleaned up. */ 1440 SVN_ERR(svn_fs_base__add_txn_copy(fs, txn_id, copy_id, trail, pool)); 1441 } 1442 else /* don't preserve history */ 1443 { 1444 id = svn_fs_base__dag_get_id(from_node); 1445 } 1446 1447 /* Set the entry in to_node to the new id. */ 1448 return svn_fs_base__dag_set_entry(to_node, entry, id, txn_id, 1449 trail, pool); 1450} 1451 1452 1453 1454/*** Deltification ***/ 1455 1456/* Maybe change the representation identified by TARGET_REP_KEY to be 1457 a delta against the representation identified by SOURCE_REP_KEY. 1458 Some reasons why we wouldn't include: 1459 1460 - TARGET_REP_KEY and SOURCE_REP_KEY are the same key. 1461 1462 - TARGET_REP_KEY's representation isn't mutable in TXN_ID (if 1463 TXN_ID is non-NULL). 1464 1465 - The delta provides less space savings that a fulltext (this is 1466 a detail handled by lower logic layers, not this function). 1467 1468 Do this work in TRAIL, using POOL for necessary allocations. 1469*/ 1470static svn_error_t * 1471maybe_deltify_mutable_rep(const char *target_rep_key, 1472 const char *source_rep_key, 1473 const char *txn_id, 1474 trail_t *trail, 1475 apr_pool_t *pool) 1476{ 1477 if (! (target_rep_key && source_rep_key 1478 && (strcmp(target_rep_key, source_rep_key) != 0))) 1479 return SVN_NO_ERROR; 1480 1481 if (txn_id) 1482 { 1483 representation_t *target_rep; 1484 SVN_ERR(svn_fs_bdb__read_rep(&target_rep, trail->fs, target_rep_key, 1485 trail, pool)); 1486 if (strcmp(target_rep->txn_id, txn_id) != 0) 1487 return SVN_NO_ERROR; 1488 } 1489 1490 return svn_fs_base__rep_deltify(trail->fs, target_rep_key, source_rep_key, 1491 trail, pool); 1492} 1493 1494 1495svn_error_t * 1496svn_fs_base__dag_deltify(dag_node_t *target, 1497 dag_node_t *source, 1498 svn_boolean_t props_only, 1499 const char *txn_id, 1500 trail_t *trail, 1501 apr_pool_t *pool) 1502{ 1503 node_revision_t *source_nr, *target_nr; 1504 svn_fs_t *fs = svn_fs_base__dag_get_fs(target); 1505 1506 /* Get node revisions for the two nodes. */ 1507 SVN_ERR(svn_fs_bdb__get_node_revision(&target_nr, fs, target->id, 1508 trail, pool)); 1509 SVN_ERR(svn_fs_bdb__get_node_revision(&source_nr, fs, source->id, 1510 trail, pool)); 1511 1512 /* If TARGET and SOURCE both have properties, and are not sharing a 1513 property key, deltify TARGET's properties. */ 1514 SVN_ERR(maybe_deltify_mutable_rep(target_nr->prop_key, source_nr->prop_key, 1515 txn_id, trail, pool)); 1516 1517 /* If we are not only attending to properties, and if TARGET and 1518 SOURCE both have data, and are not sharing a data key, deltify 1519 TARGET's data. */ 1520 if (! props_only) 1521 SVN_ERR(maybe_deltify_mutable_rep(target_nr->data_key, source_nr->data_key, 1522 txn_id, trail, pool)); 1523 1524 return SVN_NO_ERROR; 1525} 1526 1527/* Maybe store a `checksum-reps' index record for the representation whose 1528 key is REP. (If there's already a rep for this checksum, we don't 1529 bother overwriting it.) */ 1530static svn_error_t * 1531maybe_store_checksum_rep(const char *rep, 1532 trail_t *trail, 1533 apr_pool_t *pool) 1534{ 1535 svn_error_t *err = SVN_NO_ERROR; 1536 svn_fs_t *fs = trail->fs; 1537 svn_checksum_t *sha1_checksum; 1538 1539 /* We want the SHA1 checksum, if any. */ 1540 SVN_ERR(svn_fs_base__rep_contents_checksums(NULL, &sha1_checksum, 1541 fs, rep, trail, pool)); 1542 if (sha1_checksum) 1543 { 1544 err = svn_fs_bdb__set_checksum_rep(fs, sha1_checksum, rep, trail, pool); 1545 if (err && (err->apr_err == SVN_ERR_FS_ALREADY_EXISTS)) 1546 { 1547 svn_error_clear(err); 1548 err = SVN_NO_ERROR; 1549 } 1550 } 1551 return svn_error_trace(err); 1552} 1553 1554svn_error_t * 1555svn_fs_base__dag_index_checksums(dag_node_t *node, 1556 trail_t *trail, 1557 apr_pool_t *pool) 1558{ 1559 node_revision_t *node_rev; 1560 1561 SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, trail->fs, node->id, 1562 trail, pool)); 1563 if ((node_rev->kind == svn_node_file) && node_rev->data_key) 1564 SVN_ERR(maybe_store_checksum_rep(node_rev->data_key, trail, pool)); 1565 if (node_rev->prop_key) 1566 SVN_ERR(maybe_store_checksum_rep(node_rev->prop_key, trail, pool)); 1567 1568 return SVN_NO_ERROR; 1569} 1570 1571 1572 1573/*** Committing ***/ 1574 1575svn_error_t * 1576svn_fs_base__dag_commit_txn(svn_revnum_t *new_rev, 1577 svn_fs_txn_t *txn, 1578 trail_t *trail, 1579 apr_pool_t *pool) 1580{ 1581 revision_t revision; 1582 svn_string_t date; 1583 apr_hash_t *txnprops; 1584 svn_fs_t *fs = txn->fs; 1585 const char *txn_id = txn->id; 1586 1587 /* Remove any temporary transaction properties initially created by 1588 begin_txn(). */ 1589 SVN_ERR(svn_fs_base__txn_proplist_in_trail(&txnprops, txn_id, trail)); 1590 1591 /* Add new revision entry to `revisions' table. */ 1592 revision.txn_id = txn_id; 1593 *new_rev = SVN_INVALID_REVNUM; 1594 SVN_ERR(svn_fs_bdb__put_rev(new_rev, fs, &revision, trail, pool)); 1595 1596 if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD)) 1597 SVN_ERR(svn_fs_base__set_txn_prop 1598 (fs, txn_id, SVN_FS__PROP_TXN_CHECK_OOD, NULL, trail, pool)); 1599 1600 if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS)) 1601 SVN_ERR(svn_fs_base__set_txn_prop 1602 (fs, txn_id, SVN_FS__PROP_TXN_CHECK_LOCKS, NULL, trail, pool)); 1603 1604 /* Promote the unfinished transaction to a committed one. */ 1605 SVN_ERR(svn_fs_base__txn_make_committed(fs, txn_id, *new_rev, 1606 trail, pool)); 1607 1608 /* Set a date on the commit. We wait until now to fetch the date, 1609 so it's definitely newer than any previous revision's date. */ 1610 date.data = svn_time_to_cstring(apr_time_now(), pool); 1611 date.len = strlen(date.data); 1612 return svn_fs_base__set_rev_prop(fs, *new_rev, SVN_PROP_REVISION_DATE, 1613 NULL, &date, trail, pool); 1614} 1615 1616 1617/*** Comparison. ***/ 1618 1619svn_error_t * 1620svn_fs_base__things_different(svn_boolean_t *props_changed, 1621 svn_boolean_t *contents_changed, 1622 dag_node_t *node1, 1623 dag_node_t *node2, 1624 trail_t *trail, 1625 apr_pool_t *pool) 1626{ 1627 node_revision_t *noderev1, *noderev2; 1628 1629 /* If we have no place to store our results, don't bother doing 1630 anything. */ 1631 if (! props_changed && ! contents_changed) 1632 return SVN_NO_ERROR; 1633 1634 /* The node revision skels for these two nodes. */ 1635 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev1, node1->fs, node1->id, 1636 trail, pool)); 1637 SVN_ERR(svn_fs_bdb__get_node_revision(&noderev2, node2->fs, node2->id, 1638 trail, pool)); 1639 1640 /* Compare property keys. */ 1641 if (props_changed != NULL) 1642 *props_changed = (! svn_fs_base__same_keys(noderev1->prop_key, 1643 noderev2->prop_key)); 1644 1645 /* Compare contents keys and their (optional) uniquifiers. */ 1646 if (contents_changed != NULL) 1647 *contents_changed = 1648 (! (svn_fs_base__same_keys(noderev1->data_key, 1649 noderev2->data_key) 1650 /* Technically, these uniquifiers aren't used and "keys", 1651 but keys are base-36 stringified numbers, so we'll take 1652 this liberty. */ 1653 && (svn_fs_base__same_keys(noderev1->data_key_uniquifier, 1654 noderev2->data_key_uniquifier)))); 1655 1656 return SVN_NO_ERROR; 1657} 1658 1659 1660 1661/*** Mergeinfo tracking stuff ***/ 1662 1663svn_error_t * 1664svn_fs_base__dag_get_mergeinfo_stats(svn_boolean_t *has_mergeinfo, 1665 apr_int64_t *count, 1666 dag_node_t *node, 1667 trail_t *trail, 1668 apr_pool_t *pool) 1669{ 1670 node_revision_t *node_rev; 1671 svn_fs_t *fs = svn_fs_base__dag_get_fs(node); 1672 const svn_fs_id_t *id = svn_fs_base__dag_get_id(node); 1673 1674 SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, fs, id, trail, pool)); 1675 if (has_mergeinfo) 1676 *has_mergeinfo = node_rev->has_mergeinfo; 1677 if (count) 1678 *count = node_rev->mergeinfo_count; 1679 return SVN_NO_ERROR; 1680} 1681 1682 1683svn_error_t * 1684svn_fs_base__dag_set_has_mergeinfo(dag_node_t *node, 1685 svn_boolean_t has_mergeinfo, 1686 svn_boolean_t *had_mergeinfo, 1687 const char *txn_id, 1688 trail_t *trail, 1689 apr_pool_t *pool) 1690{ 1691 node_revision_t *node_rev; 1692 svn_fs_t *fs = svn_fs_base__dag_get_fs(node); 1693 const svn_fs_id_t *id = svn_fs_base__dag_get_id(node); 1694 1695 SVN_ERR(svn_fs_base__test_required_feature_format 1696 (trail->fs, "mergeinfo", SVN_FS_BASE__MIN_MERGEINFO_FORMAT)); 1697 1698 if (! svn_fs_base__dag_check_mutable(node, txn_id)) 1699 return svn_error_createf(SVN_ERR_FS_NOT_MUTABLE, NULL, 1700 _("Attempted merge tracking info change on " 1701 "immutable node")); 1702 1703 SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, fs, id, trail, pool)); 1704 *had_mergeinfo = node_rev->has_mergeinfo; 1705 1706 /* Are we changing the node? */ 1707 if ((! has_mergeinfo) != (! *had_mergeinfo)) 1708 { 1709 /* Note the new has-mergeinfo state. */ 1710 node_rev->has_mergeinfo = has_mergeinfo; 1711 1712 /* Increment or decrement the mergeinfo count as necessary. */ 1713 if (has_mergeinfo) 1714 node_rev->mergeinfo_count++; 1715 else 1716 node_rev->mergeinfo_count--; 1717 1718 SVN_ERR(svn_fs_bdb__put_node_revision(fs, id, node_rev, trail, pool)); 1719 } 1720 return SVN_NO_ERROR; 1721} 1722 1723 1724svn_error_t * 1725svn_fs_base__dag_adjust_mergeinfo_count(dag_node_t *node, 1726 apr_int64_t count_delta, 1727 const char *txn_id, 1728 trail_t *trail, 1729 apr_pool_t *pool) 1730{ 1731 node_revision_t *node_rev; 1732 svn_fs_t *fs = svn_fs_base__dag_get_fs(node); 1733 const svn_fs_id_t *id = svn_fs_base__dag_get_id(node); 1734 1735 SVN_ERR(svn_fs_base__test_required_feature_format 1736 (trail->fs, "mergeinfo", SVN_FS_BASE__MIN_MERGEINFO_FORMAT)); 1737 1738 if (! svn_fs_base__dag_check_mutable(node, txn_id)) 1739 return svn_error_createf(SVN_ERR_FS_NOT_MUTABLE, NULL, 1740 _("Attempted mergeinfo count change on " 1741 "immutable node")); 1742 1743 if (count_delta == 0) 1744 return SVN_NO_ERROR; 1745 1746 SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, fs, id, trail, pool)); 1747 node_rev->mergeinfo_count = node_rev->mergeinfo_count + count_delta; 1748 if ((node_rev->mergeinfo_count < 0) 1749 || ((node->kind == svn_node_file) && (node_rev->mergeinfo_count > 1))) 1750 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 1751 apr_psprintf(pool, 1752 _("Invalid value (%%%s) for node " 1753 "revision mergeinfo count"), 1754 APR_INT64_T_FMT), 1755 node_rev->mergeinfo_count); 1756 1757 return svn_fs_bdb__put_node_revision(fs, id, node_rev, trail, pool); 1758} 1759