1/* strings-table.c : operations on the `strings' 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#include "svn_fs.h" 25#include "svn_pools.h" 26#include "../fs.h" 27#include "../err.h" 28#include "dbt.h" 29#include "../trail.h" 30#include "../key-gen.h" 31#include "../../libsvn_fs/fs-loader.h" 32#include "bdb-err.h" 33#include "strings-table.h" 34 35#include "svn_private_config.h" 36 37 38/*** Creating and opening the strings table. ***/ 39 40int 41svn_fs_bdb__open_strings_table(DB **strings_p, 42 DB_ENV *env, 43 svn_boolean_t create) 44{ 45 const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0); 46 DB *strings; 47 48 BDB_ERR(svn_fs_bdb__check_version()); 49 BDB_ERR(db_create(&strings, env, 0)); 50 51 /* Enable duplicate keys. This allows the data to be spread out across 52 multiple records. Note: this must occur before ->open(). */ 53 BDB_ERR(strings->set_flags(strings, DB_DUP)); 54 55 BDB_ERR((strings->open)(SVN_BDB_OPEN_PARAMS(strings, NULL), 56 "strings", 0, DB_BTREE, 57 open_flags, 0666)); 58 59 if (create) 60 { 61 DBT key, value; 62 63 /* Create the `next-key' table entry. */ 64 BDB_ERR(strings->put 65 (strings, 0, 66 svn_fs_base__str_to_dbt(&key, NEXT_KEY_KEY), 67 svn_fs_base__str_to_dbt(&value, "0"), 0)); 68 } 69 70 *strings_p = strings; 71 return 0; 72} 73 74 75 76/*** Storing and retrieving strings. ***/ 77 78/* Allocate *CURSOR and advance it to first row in the set of rows 79 whose key is defined by QUERY. Set *LENGTH to the size of that 80 first row. */ 81static svn_error_t * 82locate_key(apr_size_t *length, 83 DBC **cursor, 84 DBT *query, 85 svn_fs_t *fs, 86 trail_t *trail, 87 apr_pool_t *pool) 88{ 89 base_fs_data_t *bfd = fs->fsap_data; 90 int db_err; 91 DBT result; 92 93 svn_fs_base__trail_debug(trail, "strings", "cursor"); 94 SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading a string"), 95 bfd->strings->cursor(bfd->strings, trail->db_txn, 96 cursor, 0))); 97 98 /* Set up the DBT for reading the length of the record. */ 99 svn_fs_base__clear_dbt(&result); 100 result.ulen = 0; 101 result.flags |= DB_DBT_USERMEM; 102 103 /* Advance the cursor to the key that we're looking for. */ 104 db_err = svn_bdb_dbc_get(*cursor, query, &result, DB_SET); 105 106 /* We don't need to svn_fs_base__track_dbt() the result, because nothing 107 was allocated in it. */ 108 109 /* If there's no such node, return an appropriately specific error. */ 110 if (db_err == DB_NOTFOUND) 111 { 112 svn_bdb_dbc_close(*cursor); 113 return svn_error_createf 114 (SVN_ERR_FS_NO_SUCH_STRING, 0, 115 "No such string '%s'", (const char *)query->data); 116 } 117 if (db_err) 118 { 119 DBT rerun; 120 121 if (db_err != SVN_BDB_DB_BUFFER_SMALL) 122 { 123 svn_bdb_dbc_close(*cursor); 124 return BDB_WRAP(fs, N_("moving cursor"), db_err); 125 } 126 127 /* We got an SVN_BDB_DB_BUFFER_SMALL (typical since we have a 128 zero length buf), so we need to re-run the operation to make 129 it happen. */ 130 svn_fs_base__clear_dbt(&rerun); 131 rerun.flags |= DB_DBT_USERMEM | DB_DBT_PARTIAL; 132 db_err = svn_bdb_dbc_get(*cursor, query, &rerun, DB_SET); 133 if (db_err) 134 { 135 svn_bdb_dbc_close(*cursor); 136 return BDB_WRAP(fs, N_("rerunning cursor move"), db_err); 137 } 138 } 139 140 /* ### this cast might not be safe? */ 141 *length = (apr_size_t) result.size; 142 143 return SVN_NO_ERROR; 144} 145 146 147/* Advance CURSOR by a single row in the set of rows whose keys match 148 CURSOR's current location. Set *LENGTH to the size of that next 149 row. If any error occurs, CURSOR will be destroyed. */ 150static int 151get_next_length(apr_size_t *length, DBC *cursor, DBT *query) 152{ 153 DBT result; 154 int db_err; 155 156 /* Set up the DBT for reading the length of the record. */ 157 svn_fs_base__clear_dbt(&result); 158 result.ulen = 0; 159 result.flags |= DB_DBT_USERMEM; 160 161 /* Note: this may change the QUERY DBT, but that's okay: we're going 162 to be sticking with the same key anyways. */ 163 db_err = svn_bdb_dbc_get(cursor, query, &result, DB_NEXT_DUP); 164 165 /* Note that we exit on DB_NOTFOUND. The caller uses that to end a loop. */ 166 if (db_err) 167 { 168 DBT rerun; 169 170 if (db_err != SVN_BDB_DB_BUFFER_SMALL) 171 { 172 svn_bdb_dbc_close(cursor); 173 return db_err; 174 } 175 176 /* We got an SVN_BDB_DB_BUFFER_SMALL (typical since we have a 177 zero length buf), so we need to re-run the operation to make 178 it happen. */ 179 svn_fs_base__clear_dbt(&rerun); 180 rerun.flags |= DB_DBT_USERMEM | DB_DBT_PARTIAL; 181 db_err = svn_bdb_dbc_get(cursor, query, &rerun, DB_NEXT_DUP); 182 if (db_err) 183 svn_bdb_dbc_close(cursor); 184 } 185 186 /* ### this cast might not be safe? */ 187 *length = (apr_size_t) result.size; 188 return db_err; 189} 190 191 192svn_error_t * 193svn_fs_bdb__string_read(svn_fs_t *fs, 194 const char *key, 195 char *buf, 196 svn_filesize_t offset, 197 apr_size_t *len, 198 trail_t *trail, 199 apr_pool_t *pool) 200{ 201 int db_err; 202 DBT query, result; 203 DBC *cursor; 204 apr_size_t length, bytes_read = 0; 205 206 svn_fs_base__str_to_dbt(&query, key); 207 208 SVN_ERR(locate_key(&length, &cursor, &query, fs, trail, pool)); 209 210 /* Seek through the records for this key, trying to find the record that 211 includes OFFSET. Note that we don't require reading from more than 212 one record since we're allowed to return partial reads. */ 213 while (length <= offset) 214 { 215 offset -= length; 216 217 /* Remember, if any error happens, our cursor has been closed 218 for us. */ 219 db_err = get_next_length(&length, cursor, &query); 220 221 /* No more records? They tried to read past the end. */ 222 if (db_err == DB_NOTFOUND) 223 { 224 *len = 0; 225 return SVN_NO_ERROR; 226 } 227 if (db_err) 228 return BDB_WRAP(fs, N_("reading string"), db_err); 229 } 230 231 /* The current record contains OFFSET. Fetch the contents now. Note that 232 OFFSET has been moved to be relative to this record. The length could 233 quite easily extend past this record, so we use DB_DBT_PARTIAL and 234 read successive records until we've filled the request. */ 235 while (1) 236 { 237 svn_fs_base__clear_dbt(&result); 238 result.data = buf + bytes_read; 239 result.ulen = (u_int32_t)(*len - bytes_read); 240 result.doff = (u_int32_t)offset; 241 result.dlen = result.ulen; 242 result.flags |= (DB_DBT_USERMEM | DB_DBT_PARTIAL); 243 db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_CURRENT); 244 if (db_err) 245 { 246 svn_bdb_dbc_close(cursor); 247 return BDB_WRAP(fs, N_("reading string"), db_err); 248 } 249 250 bytes_read += result.size; 251 if (bytes_read == *len) 252 { 253 /* Done with the cursor. */ 254 SVN_ERR(BDB_WRAP(fs, N_("closing string-reading cursor"), 255 svn_bdb_dbc_close(cursor))); 256 break; 257 } 258 259 /* Remember, if any error happens, our cursor has been closed 260 for us. */ 261 db_err = get_next_length(&length, cursor, &query); 262 if (db_err == DB_NOTFOUND) 263 break; 264 if (db_err) 265 return BDB_WRAP(fs, N_("reading string"), db_err); 266 267 /* We'll be reading from the beginning of the next record */ 268 offset = 0; 269 } 270 271 *len = bytes_read; 272 return SVN_NO_ERROR; 273} 274 275 276/* Get the current 'next-key' value and bump the record. */ 277static svn_error_t * 278get_key_and_bump(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 char next_key[MAX_KEY_SIZE]; 286 apr_size_t key_len; 287 int db_err; 288 DBT query; 289 DBT result; 290 291 /* ### todo: see issue #409 for why bumping the key as part of this 292 trail is problematic. */ 293 294 /* Open a cursor and move it to the 'next-key' value. We can then fetch 295 the contents and use the cursor to overwrite those contents. Since 296 this database allows duplicates, we can't do an arbitrary 'put' to 297 write the new value -- that would append, not overwrite. */ 298 299 svn_fs_base__trail_debug(trail, "strings", "cursor"); 300 SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading a string"), 301 bfd->strings->cursor(bfd->strings, trail->db_txn, 302 &cursor, 0))); 303 304 /* Advance the cursor to 'next-key' and read it. */ 305 306 db_err = svn_bdb_dbc_get(cursor, 307 svn_fs_base__str_to_dbt(&query, NEXT_KEY_KEY), 308 svn_fs_base__result_dbt(&result), 309 DB_SET); 310 if (db_err) 311 { 312 svn_bdb_dbc_close(cursor); 313 return BDB_WRAP(fs, N_("getting next-key value"), db_err); 314 } 315 316 svn_fs_base__track_dbt(&result, pool); 317 *key = apr_pstrmemdup(pool, result.data, result.size); 318 319 /* Bump to future key. */ 320 key_len = result.size; 321 svn_fs_base__next_key(result.data, &key_len, next_key); 322 323 /* Shove the new key back into the database, at the cursor position. */ 324 db_err = svn_bdb_dbc_put(cursor, &query, 325 svn_fs_base__str_to_dbt(&result, next_key), 326 DB_CURRENT); 327 if (db_err) 328 { 329 svn_bdb_dbc_close(cursor); /* ignore the error, the original is 330 more important. */ 331 return BDB_WRAP(fs, N_("bumping next string key"), db_err); 332 } 333 334 return BDB_WRAP(fs, N_("closing string-reading cursor"), 335 svn_bdb_dbc_close(cursor)); 336} 337 338svn_error_t * 339svn_fs_bdb__string_append(svn_fs_t *fs, 340 const char **key, 341 apr_size_t len, 342 const char *buf, 343 trail_t *trail, 344 apr_pool_t *pool) 345{ 346 base_fs_data_t *bfd = fs->fsap_data; 347 DBT query, result; 348 349 /* If the passed-in key is NULL, we graciously generate a new string 350 using the value of the `next-key' record in the strings table. */ 351 if (*key == NULL) 352 { 353 SVN_ERR(get_key_and_bump(fs, key, trail, pool)); 354 } 355 356 /* Store a new record into the database. */ 357 svn_fs_base__trail_debug(trail, "strings", "put"); 358 return BDB_WRAP(fs, N_("appending string"), 359 bfd->strings->put 360 (bfd->strings, trail->db_txn, 361 svn_fs_base__str_to_dbt(&query, *key), 362 svn_fs_base__set_dbt(&result, buf, len), 363 0)); 364} 365 366 367svn_error_t * 368svn_fs_bdb__string_clear(svn_fs_t *fs, 369 const char *key, 370 trail_t *trail, 371 apr_pool_t *pool) 372{ 373 base_fs_data_t *bfd = fs->fsap_data; 374 int db_err; 375 DBT query, result; 376 377 svn_fs_base__str_to_dbt(&query, key); 378 379 /* Torch the prior contents */ 380 svn_fs_base__trail_debug(trail, "strings", "del"); 381 db_err = bfd->strings->del(bfd->strings, trail->db_txn, &query, 0); 382 383 /* If there's no such node, return an appropriately specific error. */ 384 if (db_err == DB_NOTFOUND) 385 return svn_error_createf 386 (SVN_ERR_FS_NO_SUCH_STRING, 0, 387 "No such string '%s'", key); 388 389 /* Handle any other error conditions. */ 390 SVN_ERR(BDB_WRAP(fs, N_("clearing string"), db_err)); 391 392 /* Shove empty data back in for this key. */ 393 svn_fs_base__clear_dbt(&result); 394 result.data = 0; 395 result.size = 0; 396 result.flags |= DB_DBT_USERMEM; 397 398 svn_fs_base__trail_debug(trail, "strings", "put"); 399 return BDB_WRAP(fs, N_("storing empty contents"), 400 bfd->strings->put(bfd->strings, trail->db_txn, 401 &query, &result, 0)); 402} 403 404 405svn_error_t * 406svn_fs_bdb__string_size(svn_filesize_t *size, 407 svn_fs_t *fs, 408 const char *key, 409 trail_t *trail, 410 apr_pool_t *pool) 411{ 412 int db_err; 413 DBT query; 414 DBC *cursor; 415 apr_size_t length; 416 svn_filesize_t total; 417 418 svn_fs_base__str_to_dbt(&query, key); 419 420 SVN_ERR(locate_key(&length, &cursor, &query, fs, trail, pool)); 421 422 total = length; 423 while (1) 424 { 425 /* Remember, if any error happens, our cursor has been closed 426 for us. */ 427 db_err = get_next_length(&length, cursor, &query); 428 429 /* No more records? Then return the total length. */ 430 if (db_err == DB_NOTFOUND) 431 { 432 *size = total; 433 return SVN_NO_ERROR; 434 } 435 if (db_err) 436 return BDB_WRAP(fs, N_("fetching string length"), db_err); 437 438 total += length; 439 } 440 441 /* NOTREACHED */ 442} 443 444 445svn_error_t * 446svn_fs_bdb__string_delete(svn_fs_t *fs, 447 const char *key, 448 trail_t *trail, 449 apr_pool_t *pool) 450{ 451 base_fs_data_t *bfd = fs->fsap_data; 452 int db_err; 453 DBT query; 454 455 svn_fs_base__trail_debug(trail, "strings", "del"); 456 db_err = bfd->strings->del(bfd->strings, trail->db_txn, 457 svn_fs_base__str_to_dbt(&query, key), 0); 458 459 /* If there's no such node, return an appropriately specific error. */ 460 if (db_err == DB_NOTFOUND) 461 return svn_error_createf 462 (SVN_ERR_FS_NO_SUCH_STRING, 0, 463 "No such string '%s'", key); 464 465 /* Handle any other error conditions. */ 466 return BDB_WRAP(fs, N_("deleting string"), db_err); 467} 468 469 470svn_error_t * 471svn_fs_bdb__string_copy(svn_fs_t *fs, 472 const char **new_key, 473 const char *key, 474 trail_t *trail, 475 apr_pool_t *pool) 476{ 477 base_fs_data_t *bfd = fs->fsap_data; 478 DBT query; 479 DBT result; 480 DBT copykey; 481 DBC *cursor; 482 int db_err; 483 484 /* Copy off the old key in case the caller is sharing storage 485 between the old and new keys. */ 486 const char *old_key = apr_pstrdup(pool, key); 487 488 SVN_ERR(get_key_and_bump(fs, new_key, trail, pool)); 489 490 svn_fs_base__trail_debug(trail, "strings", "cursor"); 491 SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading a string"), 492 bfd->strings->cursor(bfd->strings, trail->db_txn, 493 &cursor, 0))); 494 495 svn_fs_base__str_to_dbt(&query, old_key); 496 svn_fs_base__str_to_dbt(©key, *new_key); 497 498 svn_fs_base__clear_dbt(&result); 499 500 /* Move to the first record and fetch its data (under BDB's mem mgmt). */ 501 db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_SET); 502 if (db_err) 503 { 504 svn_bdb_dbc_close(cursor); 505 return BDB_WRAP(fs, N_("getting next-key value"), db_err); 506 } 507 508 while (1) 509 { 510 /* ### can we pass a BDB-provided buffer to another BDB function? 511 ### they are supposed to have a duration up to certain points 512 ### of calling back into BDB, but I'm not sure what the exact 513 ### rules are. it is definitely nicer to use BDB buffers here 514 ### to simplify things and reduce copies, but... hrm. 515 */ 516 517 /* Write the data to the database */ 518 svn_fs_base__trail_debug(trail, "strings", "put"); 519 db_err = bfd->strings->put(bfd->strings, trail->db_txn, 520 ©key, &result, 0); 521 if (db_err) 522 { 523 svn_bdb_dbc_close(cursor); 524 return BDB_WRAP(fs, N_("writing copied data"), db_err); 525 } 526 527 /* Read the next chunk. Terminate loop if we're done. */ 528 svn_fs_base__clear_dbt(&result); 529 db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_NEXT_DUP); 530 if (db_err == DB_NOTFOUND) 531 break; 532 if (db_err) 533 { 534 svn_bdb_dbc_close(cursor); 535 return BDB_WRAP(fs, N_("fetching string data for a copy"), db_err); 536 } 537 } 538 539 return BDB_WRAP(fs, N_("closing string-reading cursor"), 540 svn_bdb_dbc_close(cursor)); 541} 542