1/* changes-table.c : operations on the `changes' table 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 "bdb_compat.h" 24 25#include <apr_hash.h> 26#include <apr_tables.h> 27 28#include "svn_hash.h" 29#include "svn_fs.h" 30#include "svn_pools.h" 31#include "svn_path.h" 32#include "../fs.h" 33#include "../err.h" 34#include "../trail.h" 35#include "../id.h" 36#include "../util/fs_skels.h" 37#include "../../libsvn_fs/fs-loader.h" 38#include "bdb-err.h" 39#include "dbt.h" 40#include "changes-table.h" 41 42#include "private/svn_fs_util.h" 43#include "private/svn_fspath.h" 44#include "svn_private_config.h" 45 46 47/*** Creating and opening the changes table. ***/ 48 49int 50svn_fs_bdb__open_changes_table(DB **changes_p, 51 DB_ENV *env, 52 svn_boolean_t create) 53{ 54 const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0); 55 DB *changes; 56 57 BDB_ERR(svn_fs_bdb__check_version()); 58 BDB_ERR(db_create(&changes, env, 0)); 59 60 /* Enable duplicate keys. This allows us to store the changes 61 one-per-row. Note: this must occur before ->open(). */ 62 BDB_ERR(changes->set_flags(changes, DB_DUP)); 63 64 BDB_ERR((changes->open)(SVN_BDB_OPEN_PARAMS(changes, NULL), 65 "changes", 0, DB_BTREE, 66 open_flags, 0666)); 67 68 *changes_p = changes; 69 return 0; 70} 71 72 73 74/*** Storing and retrieving changes. ***/ 75 76svn_error_t * 77svn_fs_bdb__changes_add(svn_fs_t *fs, 78 const char *key, 79 change_t *change, 80 trail_t *trail, 81 apr_pool_t *pool) 82{ 83 base_fs_data_t *bfd = fs->fsap_data; 84 DBT query, value; 85 svn_skel_t *skel; 86 87 /* Convert native type to skel. */ 88 SVN_ERR(svn_fs_base__unparse_change_skel(&skel, change, pool)); 89 90 /* Store a new record into the database. */ 91 svn_fs_base__str_to_dbt(&query, key); 92 svn_fs_base__skel_to_dbt(&value, skel, pool); 93 svn_fs_base__trail_debug(trail, "changes", "put"); 94 return BDB_WRAP(fs, N_("creating change"), 95 bfd->changes->put(bfd->changes, trail->db_txn, 96 &query, &value, 0)); 97} 98 99 100svn_error_t * 101svn_fs_bdb__changes_delete(svn_fs_t *fs, 102 const char *key, 103 trail_t *trail, 104 apr_pool_t *pool) 105{ 106 int db_err; 107 DBT query; 108 base_fs_data_t *bfd = fs->fsap_data; 109 110 svn_fs_base__trail_debug(trail, "changes", "del"); 111 db_err = bfd->changes->del(bfd->changes, trail->db_txn, 112 svn_fs_base__str_to_dbt(&query, key), 0); 113 114 /* If there're no changes for KEY, that is acceptable. Any other 115 error should be propagated to the caller, though. */ 116 if ((db_err) && (db_err != DB_NOTFOUND)) 117 { 118 SVN_ERR(BDB_WRAP(fs, N_("deleting changes"), db_err)); 119 } 120 121 return SVN_NO_ERROR; 122} 123 124/* Return a deep FS API type copy of SOURCE in internal format and allocate 125 * the result in RESULT_POOL. 126 */ 127static svn_fs_path_change2_t * 128change_to_fs_change(const change_t *change, 129 apr_pool_t *result_pool) 130{ 131 svn_fs_path_change2_t *result = svn_fs__path_change_create_internal( 132 svn_fs_base__id_copy(change->noderev_id, 133 result_pool), 134 change->kind, 135 result_pool); 136 result->text_mod = change->text_mod; 137 result->prop_mod = change->prop_mod; 138 result->node_kind = svn_node_unknown; 139 result->copyfrom_known = FALSE; 140 141 return result; 142} 143 144/* Merge the internal-use-only CHANGE into a hash of public-FS 145 svn_fs_path_change2_t CHANGES, collapsing multiple changes into a 146 single succinct change per path. */ 147static svn_error_t * 148fold_change(apr_hash_t *changes, 149 apr_hash_t *deletions, 150 const change_t *change) 151{ 152 apr_pool_t *pool = apr_hash_pool_get(changes); 153 svn_fs_path_change2_t *old_change, *new_change; 154 const char *path; 155 156 if ((old_change = svn_hash_gets(changes, change->path))) 157 { 158 /* This path already exists in the hash, so we have to merge 159 this change into the already existing one. */ 160 161 /* Since the path already exists in the hash, we don't have to 162 dup the allocation for the path itself. */ 163 path = change->path; 164 165 /* Sanity check: only allow NULL node revision ID in the 166 `reset' case. */ 167 if ((! change->noderev_id) && (change->kind != svn_fs_path_change_reset)) 168 return svn_error_create 169 (SVN_ERR_FS_CORRUPT, NULL, 170 _("Missing required node revision ID")); 171 172 /* Sanity check: we should be talking about the same node 173 revision ID as our last change except where the last change 174 was a deletion. */ 175 if (change->noderev_id 176 && (! svn_fs_base__id_eq(old_change->node_rev_id, 177 change->noderev_id)) 178 && (old_change->change_kind != svn_fs_path_change_delete)) 179 return svn_error_create 180 (SVN_ERR_FS_CORRUPT, NULL, 181 _("Invalid change ordering: new node revision ID without delete")); 182 183 /* Sanity check: an add, replacement, or reset must be the first 184 thing to follow a deletion. */ 185 if ((old_change->change_kind == svn_fs_path_change_delete) 186 && (! ((change->kind == svn_fs_path_change_replace) 187 || (change->kind == svn_fs_path_change_reset) 188 || (change->kind == svn_fs_path_change_add)))) 189 return svn_error_create 190 (SVN_ERR_FS_CORRUPT, NULL, 191 _("Invalid change ordering: non-add change on deleted path")); 192 193 /* Sanity check: an add can't follow anything except 194 a delete or reset. */ 195 if ((change->kind == svn_fs_path_change_add) 196 && (old_change->change_kind != svn_fs_path_change_delete) 197 && (old_change->change_kind != svn_fs_path_change_reset)) 198 return svn_error_create 199 (SVN_ERR_FS_CORRUPT, NULL, 200 _("Invalid change ordering: add change on preexisting path")); 201 202 /* Now, merge that change in. */ 203 switch (change->kind) 204 { 205 case svn_fs_path_change_reset: 206 /* A reset here will simply remove the path change from the 207 hash. */ 208 new_change = NULL; 209 break; 210 211 case svn_fs_path_change_delete: 212 if (old_change->change_kind == svn_fs_path_change_add) 213 { 214 /* If the path was introduced in this transaction via an 215 add, and we are deleting it, just remove the path 216 altogether. */ 217 new_change = NULL; 218 } 219 else if (old_change->change_kind == svn_fs_path_change_replace) 220 { 221 /* A deleting a 'replace' restore the original deletion. */ 222 new_change = svn_hash_gets(deletions, path); 223 SVN_ERR_ASSERT(new_change); 224 } 225 else 226 { 227 /* A deletion overrules all previous changes. */ 228 new_change = old_change; 229 new_change->change_kind = svn_fs_path_change_delete; 230 new_change->text_mod = change->text_mod; 231 new_change->prop_mod = change->prop_mod; 232 } 233 break; 234 235 case svn_fs_path_change_add: 236 case svn_fs_path_change_replace: 237 /* An add at this point must be following a previous delete, 238 so treat it just like a replace. */ 239 240 new_change = change_to_fs_change(change, pool); 241 new_change->change_kind = svn_fs_path_change_replace; 242 243 /* Remember the original deletion. 244 * Make sure to allocate the hash key in a durable pool. */ 245 svn_hash_sets(deletions, 246 apr_pstrdup(apr_hash_pool_get(deletions), path), 247 old_change); 248 break; 249 250 case svn_fs_path_change_modify: 251 default: 252 new_change = old_change; 253 if (change->text_mod) 254 new_change->text_mod = TRUE; 255 if (change->prop_mod) 256 new_change->prop_mod = TRUE; 257 break; 258 } 259 } 260 else 261 { 262 /* This change is new to the hash, so make a new public change 263 structure from the internal one (in the hash's pool), and dup 264 the path into the hash's pool, too. */ 265 new_change = change_to_fs_change(change, pool); 266 path = apr_pstrdup(pool, change->path); 267 } 268 269 /* Add (or update) this path. */ 270 svn_hash_sets(changes, path, new_change); 271 272 return SVN_NO_ERROR; 273} 274 275 276svn_error_t * 277svn_fs_bdb__changes_fetch(apr_hash_t **changes_p, 278 svn_fs_t *fs, 279 const char *key, 280 trail_t *trail, 281 apr_pool_t *pool) 282{ 283 base_fs_data_t *bfd = fs->fsap_data; 284 DBC *cursor; 285 DBT query, result; 286 int db_err = 0, db_c_err = 0; 287 svn_error_t *err = SVN_NO_ERROR; 288 apr_hash_t *changes = apr_hash_make(pool); 289 apr_pool_t *subpool = svn_pool_create(pool); 290 apr_pool_t *iterpool = svn_pool_create(pool); 291 apr_hash_t *deletions = apr_hash_make(subpool); 292 293 /* Get a cursor on the first record matching KEY, and then loop over 294 the records, adding them to the return array. */ 295 svn_fs_base__trail_debug(trail, "changes", "cursor"); 296 SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading changes"), 297 bfd->changes->cursor(bfd->changes, trail->db_txn, 298 &cursor, 0))); 299 300 /* Advance the cursor to the key that we're looking for. */ 301 svn_fs_base__str_to_dbt(&query, key); 302 svn_fs_base__result_dbt(&result); 303 db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_SET); 304 if (! db_err) 305 svn_fs_base__track_dbt(&result, pool); 306 307 while (! db_err) 308 { 309 change_t *change; 310 svn_skel_t *result_skel; 311 312 /* Clear the per-iteration subpool. */ 313 svn_pool_clear(iterpool); 314 315 /* RESULT now contains a change record associated with KEY. We 316 need to parse that skel into an change_t structure ... */ 317 result_skel = svn_skel__parse(result.data, result.size, iterpool); 318 if (! result_skel) 319 { 320 err = svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 321 _("Error reading changes for key '%s'"), 322 key); 323 goto cleanup; 324 } 325 err = svn_fs_base__parse_change_skel(&change, result_skel, iterpool); 326 if (err) 327 goto cleanup; 328 329 /* ... and merge it with our return hash. */ 330 err = fold_change(changes, deletions, change); 331 if (err) 332 goto cleanup; 333 334 /* Now, if our change was a deletion or replacement, we have to 335 blow away any changes thus far on paths that are (or, were) 336 children of this path. 337 ### i won't bother with another iteration pool here -- at 338 most we talking about a few extra dups of paths into what 339 is already a temporary subpool. 340 */ 341 if ((change->kind == svn_fs_path_change_delete) 342 || (change->kind == svn_fs_path_change_replace)) 343 { 344 apr_hash_index_t *hi; 345 346 for (hi = apr_hash_first(iterpool, changes); 347 hi; 348 hi = apr_hash_next(hi)) 349 { 350 /* KEY is the path. */ 351 const void *hashkey; 352 apr_ssize_t klen; 353 const char *child_relpath; 354 355 apr_hash_this(hi, &hashkey, &klen, NULL); 356 357 /* If we come across our own path, ignore it. 358 If we come across a child of our path, remove it. */ 359 child_relpath = svn_fspath__skip_ancestor(change->path, hashkey); 360 if (child_relpath && *child_relpath) 361 apr_hash_set(changes, hashkey, klen, NULL); 362 } 363 } 364 365 /* Advance the cursor to the next record with this same KEY, and 366 fetch that record. */ 367 svn_fs_base__result_dbt(&result); 368 db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_NEXT_DUP); 369 if (! db_err) 370 svn_fs_base__track_dbt(&result, pool); 371 } 372 373 /* Destroy the per-iteration subpool. */ 374 svn_pool_destroy(iterpool); 375 svn_pool_destroy(subpool); 376 377 /* If there are no (more) change records for this KEY, we're 378 finished. Just return the (possibly empty) array. Any other 379 error, however, needs to get handled appropriately. */ 380 if (db_err && (db_err != DB_NOTFOUND)) 381 err = BDB_WRAP(fs, N_("fetching changes"), db_err); 382 383 cleanup: 384 /* Close the cursor. */ 385 db_c_err = svn_bdb_dbc_close(cursor); 386 387 /* If we had an error prior to closing the cursor, return the error. */ 388 if (err) 389 return svn_error_trace(err); 390 391 /* If our only error thus far was when we closed the cursor, return 392 that error. */ 393 if (db_c_err) 394 SVN_ERR(BDB_WRAP(fs, N_("closing changes cursor"), db_c_err)); 395 396 /* Finally, set our return variable and get outta here. */ 397 *changes_p = changes; 398 return SVN_NO_ERROR; 399} 400 401 402svn_error_t * 403svn_fs_bdb__changes_fetch_raw(apr_array_header_t **changes_p, 404 svn_fs_t *fs, 405 const char *key, 406 trail_t *trail, 407 apr_pool_t *pool) 408{ 409 base_fs_data_t *bfd = fs->fsap_data; 410 DBC *cursor; 411 DBT query, result; 412 int db_err = 0, db_c_err = 0; 413 svn_error_t *err = SVN_NO_ERROR; 414 change_t *change; 415 apr_array_header_t *changes = apr_array_make(pool, 4, sizeof(change)); 416 417 /* Get a cursor on the first record matching KEY, and then loop over 418 the records, adding them to the return array. */ 419 svn_fs_base__trail_debug(trail, "changes", "cursor"); 420 SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading changes"), 421 bfd->changes->cursor(bfd->changes, trail->db_txn, 422 &cursor, 0))); 423 424 /* Advance the cursor to the key that we're looking for. */ 425 svn_fs_base__str_to_dbt(&query, key); 426 svn_fs_base__result_dbt(&result); 427 db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_SET); 428 if (! db_err) 429 svn_fs_base__track_dbt(&result, pool); 430 431 while (! db_err) 432 { 433 svn_skel_t *result_skel; 434 435 /* RESULT now contains a change record associated with KEY. We 436 need to parse that skel into an change_t structure ... */ 437 result_skel = svn_skel__parse(result.data, result.size, pool); 438 if (! result_skel) 439 { 440 err = svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 441 _("Error reading changes for key '%s'"), 442 key); 443 goto cleanup; 444 } 445 err = svn_fs_base__parse_change_skel(&change, result_skel, pool); 446 if (err) 447 goto cleanup; 448 449 /* ... and add it to our return array. */ 450 APR_ARRAY_PUSH(changes, change_t *) = change; 451 452 /* Advance the cursor to the next record with this same KEY, and 453 fetch that record. */ 454 svn_fs_base__result_dbt(&result); 455 db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_NEXT_DUP); 456 if (! db_err) 457 svn_fs_base__track_dbt(&result, pool); 458 } 459 460 /* If there are no (more) change records for this KEY, we're 461 finished. Just return the (possibly empty) array. Any other 462 error, however, needs to get handled appropriately. */ 463 if (db_err && (db_err != DB_NOTFOUND)) 464 err = BDB_WRAP(fs, N_("fetching changes"), db_err); 465 466 cleanup: 467 /* Close the cursor. */ 468 db_c_err = svn_bdb_dbc_close(cursor); 469 470 /* If we had an error prior to closing the cursor, return the error. */ 471 if (err) 472 return svn_error_trace(err); 473 474 /* If our only error thus far was when we closed the cursor, return 475 that error. */ 476 if (db_c_err) 477 SVN_ERR(BDB_WRAP(fs, N_("closing changes cursor"), db_c_err)); 478 479 /* Finally, set our return variable and get outta here. */ 480 *changes_p = changes; 481 return SVN_NO_ERROR; 482} 483