1251881Speter/* strings-table.c : operations on the `strings' table 2251881Speter * 3251881Speter * ==================================================================== 4251881Speter * Licensed to the Apache Software Foundation (ASF) under one 5251881Speter * or more contributor license agreements. See the NOTICE file 6251881Speter * distributed with this work for additional information 7251881Speter * regarding copyright ownership. The ASF licenses this file 8251881Speter * to you under the Apache License, Version 2.0 (the 9251881Speter * "License"); you may not use this file except in compliance 10251881Speter * with the License. You may obtain a copy of the License at 11251881Speter * 12251881Speter * http://www.apache.org/licenses/LICENSE-2.0 13251881Speter * 14251881Speter * Unless required by applicable law or agreed to in writing, 15251881Speter * software distributed under the License is distributed on an 16251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17251881Speter * KIND, either express or implied. See the License for the 18251881Speter * specific language governing permissions and limitations 19251881Speter * under the License. 20251881Speter * ==================================================================== 21251881Speter */ 22251881Speter 23251881Speter#include "bdb_compat.h" 24251881Speter#include "svn_fs.h" 25251881Speter#include "svn_pools.h" 26251881Speter#include "../fs.h" 27251881Speter#include "../err.h" 28251881Speter#include "dbt.h" 29251881Speter#include "../trail.h" 30251881Speter#include "../key-gen.h" 31251881Speter#include "../../libsvn_fs/fs-loader.h" 32251881Speter#include "bdb-err.h" 33251881Speter#include "strings-table.h" 34251881Speter 35251881Speter#include "svn_private_config.h" 36251881Speter 37251881Speter 38251881Speter/*** Creating and opening the strings table. ***/ 39251881Speter 40251881Speterint 41251881Spetersvn_fs_bdb__open_strings_table(DB **strings_p, 42251881Speter DB_ENV *env, 43251881Speter svn_boolean_t create) 44251881Speter{ 45251881Speter const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0); 46251881Speter DB *strings; 47251881Speter 48251881Speter BDB_ERR(svn_fs_bdb__check_version()); 49251881Speter BDB_ERR(db_create(&strings, env, 0)); 50251881Speter 51251881Speter /* Enable duplicate keys. This allows the data to be spread out across 52251881Speter multiple records. Note: this must occur before ->open(). */ 53251881Speter BDB_ERR(strings->set_flags(strings, DB_DUP)); 54251881Speter 55251881Speter BDB_ERR((strings->open)(SVN_BDB_OPEN_PARAMS(strings, NULL), 56251881Speter "strings", 0, DB_BTREE, 57251881Speter open_flags, 0666)); 58251881Speter 59251881Speter if (create) 60251881Speter { 61251881Speter DBT key, value; 62251881Speter 63251881Speter /* Create the `next-key' table entry. */ 64251881Speter BDB_ERR(strings->put 65251881Speter (strings, 0, 66251881Speter svn_fs_base__str_to_dbt(&key, NEXT_KEY_KEY), 67251881Speter svn_fs_base__str_to_dbt(&value, "0"), 0)); 68251881Speter } 69251881Speter 70251881Speter *strings_p = strings; 71251881Speter return 0; 72251881Speter} 73251881Speter 74251881Speter 75251881Speter 76251881Speter/*** Storing and retrieving strings. ***/ 77251881Speter 78251881Speter/* Allocate *CURSOR and advance it to first row in the set of rows 79251881Speter whose key is defined by QUERY. Set *LENGTH to the size of that 80251881Speter first row. */ 81251881Speterstatic svn_error_t * 82251881Speterlocate_key(apr_size_t *length, 83251881Speter DBC **cursor, 84251881Speter DBT *query, 85251881Speter svn_fs_t *fs, 86251881Speter trail_t *trail, 87251881Speter apr_pool_t *pool) 88251881Speter{ 89251881Speter base_fs_data_t *bfd = fs->fsap_data; 90251881Speter int db_err; 91251881Speter DBT result; 92251881Speter 93251881Speter svn_fs_base__trail_debug(trail, "strings", "cursor"); 94251881Speter SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading a string"), 95251881Speter bfd->strings->cursor(bfd->strings, trail->db_txn, 96251881Speter cursor, 0))); 97251881Speter 98251881Speter /* Set up the DBT for reading the length of the record. */ 99251881Speter svn_fs_base__clear_dbt(&result); 100251881Speter result.ulen = 0; 101251881Speter result.flags |= DB_DBT_USERMEM; 102251881Speter 103251881Speter /* Advance the cursor to the key that we're looking for. */ 104251881Speter db_err = svn_bdb_dbc_get(*cursor, query, &result, DB_SET); 105251881Speter 106251881Speter /* We don't need to svn_fs_base__track_dbt() the result, because nothing 107251881Speter was allocated in it. */ 108251881Speter 109251881Speter /* If there's no such node, return an appropriately specific error. */ 110251881Speter if (db_err == DB_NOTFOUND) 111251881Speter { 112251881Speter svn_bdb_dbc_close(*cursor); 113251881Speter return svn_error_createf 114251881Speter (SVN_ERR_FS_NO_SUCH_STRING, 0, 115251881Speter "No such string '%s'", (const char *)query->data); 116251881Speter } 117251881Speter if (db_err) 118251881Speter { 119251881Speter DBT rerun; 120251881Speter 121251881Speter if (db_err != SVN_BDB_DB_BUFFER_SMALL) 122251881Speter { 123251881Speter svn_bdb_dbc_close(*cursor); 124251881Speter return BDB_WRAP(fs, N_("moving cursor"), db_err); 125251881Speter } 126251881Speter 127251881Speter /* We got an SVN_BDB_DB_BUFFER_SMALL (typical since we have a 128251881Speter zero length buf), so we need to re-run the operation to make 129251881Speter it happen. */ 130251881Speter svn_fs_base__clear_dbt(&rerun); 131251881Speter rerun.flags |= DB_DBT_USERMEM | DB_DBT_PARTIAL; 132251881Speter db_err = svn_bdb_dbc_get(*cursor, query, &rerun, DB_SET); 133251881Speter if (db_err) 134251881Speter { 135251881Speter svn_bdb_dbc_close(*cursor); 136251881Speter return BDB_WRAP(fs, N_("rerunning cursor move"), db_err); 137251881Speter } 138251881Speter } 139251881Speter 140251881Speter /* ### this cast might not be safe? */ 141251881Speter *length = (apr_size_t) result.size; 142251881Speter 143251881Speter return SVN_NO_ERROR; 144251881Speter} 145251881Speter 146251881Speter 147251881Speter/* Advance CURSOR by a single row in the set of rows whose keys match 148251881Speter CURSOR's current location. Set *LENGTH to the size of that next 149251881Speter row. If any error occurs, CURSOR will be destroyed. */ 150251881Speterstatic int 151251881Speterget_next_length(apr_size_t *length, DBC *cursor, DBT *query) 152251881Speter{ 153251881Speter DBT result; 154251881Speter int db_err; 155251881Speter 156251881Speter /* Set up the DBT for reading the length of the record. */ 157251881Speter svn_fs_base__clear_dbt(&result); 158251881Speter result.ulen = 0; 159251881Speter result.flags |= DB_DBT_USERMEM; 160251881Speter 161251881Speter /* Note: this may change the QUERY DBT, but that's okay: we're going 162251881Speter to be sticking with the same key anyways. */ 163251881Speter db_err = svn_bdb_dbc_get(cursor, query, &result, DB_NEXT_DUP); 164251881Speter 165251881Speter /* Note that we exit on DB_NOTFOUND. The caller uses that to end a loop. */ 166251881Speter if (db_err) 167251881Speter { 168251881Speter DBT rerun; 169251881Speter 170251881Speter if (db_err != SVN_BDB_DB_BUFFER_SMALL) 171251881Speter { 172251881Speter svn_bdb_dbc_close(cursor); 173251881Speter return db_err; 174251881Speter } 175251881Speter 176251881Speter /* We got an SVN_BDB_DB_BUFFER_SMALL (typical since we have a 177251881Speter zero length buf), so we need to re-run the operation to make 178251881Speter it happen. */ 179251881Speter svn_fs_base__clear_dbt(&rerun); 180251881Speter rerun.flags |= DB_DBT_USERMEM | DB_DBT_PARTIAL; 181251881Speter db_err = svn_bdb_dbc_get(cursor, query, &rerun, DB_NEXT_DUP); 182251881Speter if (db_err) 183251881Speter svn_bdb_dbc_close(cursor); 184251881Speter } 185251881Speter 186251881Speter /* ### this cast might not be safe? */ 187251881Speter *length = (apr_size_t) result.size; 188251881Speter return db_err; 189251881Speter} 190251881Speter 191251881Speter 192251881Spetersvn_error_t * 193251881Spetersvn_fs_bdb__string_read(svn_fs_t *fs, 194251881Speter const char *key, 195251881Speter char *buf, 196251881Speter svn_filesize_t offset, 197251881Speter apr_size_t *len, 198251881Speter trail_t *trail, 199251881Speter apr_pool_t *pool) 200251881Speter{ 201251881Speter int db_err; 202251881Speter DBT query, result; 203251881Speter DBC *cursor; 204251881Speter apr_size_t length, bytes_read = 0; 205251881Speter 206251881Speter svn_fs_base__str_to_dbt(&query, key); 207251881Speter 208251881Speter SVN_ERR(locate_key(&length, &cursor, &query, fs, trail, pool)); 209251881Speter 210251881Speter /* Seek through the records for this key, trying to find the record that 211251881Speter includes OFFSET. Note that we don't require reading from more than 212251881Speter one record since we're allowed to return partial reads. */ 213251881Speter while (length <= offset) 214251881Speter { 215251881Speter offset -= length; 216251881Speter 217251881Speter /* Remember, if any error happens, our cursor has been closed 218251881Speter for us. */ 219251881Speter db_err = get_next_length(&length, cursor, &query); 220251881Speter 221251881Speter /* No more records? They tried to read past the end. */ 222251881Speter if (db_err == DB_NOTFOUND) 223251881Speter { 224251881Speter *len = 0; 225251881Speter return SVN_NO_ERROR; 226251881Speter } 227251881Speter if (db_err) 228251881Speter return BDB_WRAP(fs, N_("reading string"), db_err); 229251881Speter } 230251881Speter 231251881Speter /* The current record contains OFFSET. Fetch the contents now. Note that 232251881Speter OFFSET has been moved to be relative to this record. The length could 233251881Speter quite easily extend past this record, so we use DB_DBT_PARTIAL and 234251881Speter read successive records until we've filled the request. */ 235251881Speter while (1) 236251881Speter { 237251881Speter svn_fs_base__clear_dbt(&result); 238251881Speter result.data = buf + bytes_read; 239289180Speter result.ulen = (u_int32_t)(*len - bytes_read); 240251881Speter result.doff = (u_int32_t)offset; 241289180Speter result.dlen = result.ulen; 242251881Speter result.flags |= (DB_DBT_USERMEM | DB_DBT_PARTIAL); 243251881Speter db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_CURRENT); 244251881Speter if (db_err) 245251881Speter { 246251881Speter svn_bdb_dbc_close(cursor); 247251881Speter return BDB_WRAP(fs, N_("reading string"), db_err); 248251881Speter } 249251881Speter 250251881Speter bytes_read += result.size; 251251881Speter if (bytes_read == *len) 252251881Speter { 253251881Speter /* Done with the cursor. */ 254251881Speter SVN_ERR(BDB_WRAP(fs, N_("closing string-reading cursor"), 255251881Speter svn_bdb_dbc_close(cursor))); 256251881Speter break; 257251881Speter } 258251881Speter 259251881Speter /* Remember, if any error happens, our cursor has been closed 260251881Speter for us. */ 261251881Speter db_err = get_next_length(&length, cursor, &query); 262251881Speter if (db_err == DB_NOTFOUND) 263251881Speter break; 264251881Speter if (db_err) 265251881Speter return BDB_WRAP(fs, N_("reading string"), db_err); 266251881Speter 267251881Speter /* We'll be reading from the beginning of the next record */ 268251881Speter offset = 0; 269251881Speter } 270251881Speter 271251881Speter *len = bytes_read; 272251881Speter return SVN_NO_ERROR; 273251881Speter} 274251881Speter 275251881Speter 276251881Speter/* Get the current 'next-key' value and bump the record. */ 277251881Speterstatic svn_error_t * 278251881Speterget_key_and_bump(svn_fs_t *fs, 279251881Speter const char **key, 280251881Speter trail_t *trail, 281251881Speter apr_pool_t *pool) 282251881Speter{ 283251881Speter base_fs_data_t *bfd = fs->fsap_data; 284251881Speter DBC *cursor; 285251881Speter char next_key[MAX_KEY_SIZE]; 286251881Speter apr_size_t key_len; 287251881Speter int db_err; 288251881Speter DBT query; 289251881Speter DBT result; 290251881Speter 291251881Speter /* ### todo: see issue #409 for why bumping the key as part of this 292251881Speter trail is problematic. */ 293251881Speter 294251881Speter /* Open a cursor and move it to the 'next-key' value. We can then fetch 295251881Speter the contents and use the cursor to overwrite those contents. Since 296251881Speter this database allows duplicates, we can't do an arbitrary 'put' to 297251881Speter write the new value -- that would append, not overwrite. */ 298251881Speter 299251881Speter svn_fs_base__trail_debug(trail, "strings", "cursor"); 300251881Speter SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading a string"), 301251881Speter bfd->strings->cursor(bfd->strings, trail->db_txn, 302251881Speter &cursor, 0))); 303251881Speter 304251881Speter /* Advance the cursor to 'next-key' and read it. */ 305251881Speter 306251881Speter db_err = svn_bdb_dbc_get(cursor, 307251881Speter svn_fs_base__str_to_dbt(&query, NEXT_KEY_KEY), 308251881Speter svn_fs_base__result_dbt(&result), 309251881Speter DB_SET); 310251881Speter if (db_err) 311251881Speter { 312251881Speter svn_bdb_dbc_close(cursor); 313251881Speter return BDB_WRAP(fs, N_("getting next-key value"), db_err); 314251881Speter } 315251881Speter 316251881Speter svn_fs_base__track_dbt(&result, pool); 317251881Speter *key = apr_pstrmemdup(pool, result.data, result.size); 318251881Speter 319251881Speter /* Bump to future key. */ 320251881Speter key_len = result.size; 321251881Speter svn_fs_base__next_key(result.data, &key_len, next_key); 322251881Speter 323251881Speter /* Shove the new key back into the database, at the cursor position. */ 324251881Speter db_err = svn_bdb_dbc_put(cursor, &query, 325251881Speter svn_fs_base__str_to_dbt(&result, next_key), 326251881Speter DB_CURRENT); 327251881Speter if (db_err) 328251881Speter { 329251881Speter svn_bdb_dbc_close(cursor); /* ignore the error, the original is 330251881Speter more important. */ 331251881Speter return BDB_WRAP(fs, N_("bumping next string key"), db_err); 332251881Speter } 333251881Speter 334251881Speter return BDB_WRAP(fs, N_("closing string-reading cursor"), 335251881Speter svn_bdb_dbc_close(cursor)); 336251881Speter} 337251881Speter 338251881Spetersvn_error_t * 339251881Spetersvn_fs_bdb__string_append(svn_fs_t *fs, 340251881Speter const char **key, 341251881Speter apr_size_t len, 342251881Speter const char *buf, 343251881Speter trail_t *trail, 344251881Speter apr_pool_t *pool) 345251881Speter{ 346251881Speter base_fs_data_t *bfd = fs->fsap_data; 347251881Speter DBT query, result; 348251881Speter 349251881Speter /* If the passed-in key is NULL, we graciously generate a new string 350251881Speter using the value of the `next-key' record in the strings table. */ 351251881Speter if (*key == NULL) 352251881Speter { 353251881Speter SVN_ERR(get_key_and_bump(fs, key, trail, pool)); 354251881Speter } 355251881Speter 356251881Speter /* Store a new record into the database. */ 357251881Speter svn_fs_base__trail_debug(trail, "strings", "put"); 358251881Speter return BDB_WRAP(fs, N_("appending string"), 359251881Speter bfd->strings->put 360251881Speter (bfd->strings, trail->db_txn, 361251881Speter svn_fs_base__str_to_dbt(&query, *key), 362251881Speter svn_fs_base__set_dbt(&result, buf, len), 363251881Speter 0)); 364251881Speter} 365251881Speter 366251881Speter 367251881Spetersvn_error_t * 368251881Spetersvn_fs_bdb__string_clear(svn_fs_t *fs, 369251881Speter const char *key, 370251881Speter trail_t *trail, 371251881Speter apr_pool_t *pool) 372251881Speter{ 373251881Speter base_fs_data_t *bfd = fs->fsap_data; 374251881Speter int db_err; 375251881Speter DBT query, result; 376251881Speter 377251881Speter svn_fs_base__str_to_dbt(&query, key); 378251881Speter 379251881Speter /* Torch the prior contents */ 380251881Speter svn_fs_base__trail_debug(trail, "strings", "del"); 381251881Speter db_err = bfd->strings->del(bfd->strings, trail->db_txn, &query, 0); 382251881Speter 383251881Speter /* If there's no such node, return an appropriately specific error. */ 384251881Speter if (db_err == DB_NOTFOUND) 385251881Speter return svn_error_createf 386251881Speter (SVN_ERR_FS_NO_SUCH_STRING, 0, 387251881Speter "No such string '%s'", key); 388251881Speter 389251881Speter /* Handle any other error conditions. */ 390251881Speter SVN_ERR(BDB_WRAP(fs, N_("clearing string"), db_err)); 391251881Speter 392251881Speter /* Shove empty data back in for this key. */ 393251881Speter svn_fs_base__clear_dbt(&result); 394251881Speter result.data = 0; 395251881Speter result.size = 0; 396251881Speter result.flags |= DB_DBT_USERMEM; 397251881Speter 398251881Speter svn_fs_base__trail_debug(trail, "strings", "put"); 399251881Speter return BDB_WRAP(fs, N_("storing empty contents"), 400251881Speter bfd->strings->put(bfd->strings, trail->db_txn, 401251881Speter &query, &result, 0)); 402251881Speter} 403251881Speter 404251881Speter 405251881Spetersvn_error_t * 406251881Spetersvn_fs_bdb__string_size(svn_filesize_t *size, 407251881Speter svn_fs_t *fs, 408251881Speter const char *key, 409251881Speter trail_t *trail, 410251881Speter apr_pool_t *pool) 411251881Speter{ 412251881Speter int db_err; 413251881Speter DBT query; 414251881Speter DBC *cursor; 415251881Speter apr_size_t length; 416251881Speter svn_filesize_t total; 417251881Speter 418251881Speter svn_fs_base__str_to_dbt(&query, key); 419251881Speter 420251881Speter SVN_ERR(locate_key(&length, &cursor, &query, fs, trail, pool)); 421251881Speter 422251881Speter total = length; 423251881Speter while (1) 424251881Speter { 425251881Speter /* Remember, if any error happens, our cursor has been closed 426251881Speter for us. */ 427251881Speter db_err = get_next_length(&length, cursor, &query); 428251881Speter 429251881Speter /* No more records? Then return the total length. */ 430251881Speter if (db_err == DB_NOTFOUND) 431251881Speter { 432251881Speter *size = total; 433251881Speter return SVN_NO_ERROR; 434251881Speter } 435251881Speter if (db_err) 436251881Speter return BDB_WRAP(fs, N_("fetching string length"), db_err); 437251881Speter 438251881Speter total += length; 439251881Speter } 440251881Speter 441251881Speter /* NOTREACHED */ 442251881Speter} 443251881Speter 444251881Speter 445251881Spetersvn_error_t * 446251881Spetersvn_fs_bdb__string_delete(svn_fs_t *fs, 447251881Speter const char *key, 448251881Speter trail_t *trail, 449251881Speter apr_pool_t *pool) 450251881Speter{ 451251881Speter base_fs_data_t *bfd = fs->fsap_data; 452251881Speter int db_err; 453251881Speter DBT query; 454251881Speter 455251881Speter svn_fs_base__trail_debug(trail, "strings", "del"); 456251881Speter db_err = bfd->strings->del(bfd->strings, trail->db_txn, 457251881Speter svn_fs_base__str_to_dbt(&query, key), 0); 458251881Speter 459251881Speter /* If there's no such node, return an appropriately specific error. */ 460251881Speter if (db_err == DB_NOTFOUND) 461251881Speter return svn_error_createf 462251881Speter (SVN_ERR_FS_NO_SUCH_STRING, 0, 463251881Speter "No such string '%s'", key); 464251881Speter 465251881Speter /* Handle any other error conditions. */ 466251881Speter return BDB_WRAP(fs, N_("deleting string"), db_err); 467251881Speter} 468251881Speter 469251881Speter 470251881Spetersvn_error_t * 471251881Spetersvn_fs_bdb__string_copy(svn_fs_t *fs, 472251881Speter const char **new_key, 473251881Speter const char *key, 474251881Speter trail_t *trail, 475251881Speter apr_pool_t *pool) 476251881Speter{ 477251881Speter base_fs_data_t *bfd = fs->fsap_data; 478251881Speter DBT query; 479251881Speter DBT result; 480251881Speter DBT copykey; 481251881Speter DBC *cursor; 482251881Speter int db_err; 483251881Speter 484251881Speter /* Copy off the old key in case the caller is sharing storage 485251881Speter between the old and new keys. */ 486251881Speter const char *old_key = apr_pstrdup(pool, key); 487251881Speter 488251881Speter SVN_ERR(get_key_and_bump(fs, new_key, trail, pool)); 489251881Speter 490251881Speter svn_fs_base__trail_debug(trail, "strings", "cursor"); 491251881Speter SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading a string"), 492251881Speter bfd->strings->cursor(bfd->strings, trail->db_txn, 493251881Speter &cursor, 0))); 494251881Speter 495251881Speter svn_fs_base__str_to_dbt(&query, old_key); 496251881Speter svn_fs_base__str_to_dbt(©key, *new_key); 497251881Speter 498251881Speter svn_fs_base__clear_dbt(&result); 499251881Speter 500251881Speter /* Move to the first record and fetch its data (under BDB's mem mgmt). */ 501251881Speter db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_SET); 502251881Speter if (db_err) 503251881Speter { 504251881Speter svn_bdb_dbc_close(cursor); 505251881Speter return BDB_WRAP(fs, N_("getting next-key value"), db_err); 506251881Speter } 507251881Speter 508251881Speter while (1) 509251881Speter { 510251881Speter /* ### can we pass a BDB-provided buffer to another BDB function? 511251881Speter ### they are supposed to have a duration up to certain points 512251881Speter ### of calling back into BDB, but I'm not sure what the exact 513251881Speter ### rules are. it is definitely nicer to use BDB buffers here 514251881Speter ### to simplify things and reduce copies, but... hrm. 515251881Speter */ 516251881Speter 517251881Speter /* Write the data to the database */ 518251881Speter svn_fs_base__trail_debug(trail, "strings", "put"); 519251881Speter db_err = bfd->strings->put(bfd->strings, trail->db_txn, 520251881Speter ©key, &result, 0); 521251881Speter if (db_err) 522251881Speter { 523251881Speter svn_bdb_dbc_close(cursor); 524251881Speter return BDB_WRAP(fs, N_("writing copied data"), db_err); 525251881Speter } 526251881Speter 527251881Speter /* Read the next chunk. Terminate loop if we're done. */ 528251881Speter svn_fs_base__clear_dbt(&result); 529251881Speter db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_NEXT_DUP); 530251881Speter if (db_err == DB_NOTFOUND) 531251881Speter break; 532251881Speter if (db_err) 533251881Speter { 534251881Speter svn_bdb_dbc_close(cursor); 535251881Speter return BDB_WRAP(fs, N_("fetching string data for a copy"), db_err); 536251881Speter } 537251881Speter } 538251881Speter 539251881Speter return BDB_WRAP(fs, N_("closing string-reading cursor"), 540251881Speter svn_bdb_dbc_close(cursor)); 541251881Speter} 542