diff options
Diffstat (limited to 'subversion/libsvn_fs_base/bdb/strings-table.c')
-rw-r--r-- | subversion/libsvn_fs_base/bdb/strings-table.c | 541 |
1 files changed, 541 insertions, 0 deletions
diff --git a/subversion/libsvn_fs_base/bdb/strings-table.c b/subversion/libsvn_fs_base/bdb/strings-table.c new file mode 100644 index 0000000..f5348e7 --- /dev/null +++ b/subversion/libsvn_fs_base/bdb/strings-table.c @@ -0,0 +1,541 @@ +/* strings-table.c : operations on the `strings' table + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include "bdb_compat.h" +#include "svn_fs.h" +#include "svn_pools.h" +#include "../fs.h" +#include "../err.h" +#include "dbt.h" +#include "../trail.h" +#include "../key-gen.h" +#include "../../libsvn_fs/fs-loader.h" +#include "bdb-err.h" +#include "strings-table.h" + +#include "svn_private_config.h" + + +/*** Creating and opening the strings table. ***/ + +int +svn_fs_bdb__open_strings_table(DB **strings_p, + DB_ENV *env, + svn_boolean_t create) +{ + const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0); + DB *strings; + + BDB_ERR(svn_fs_bdb__check_version()); + BDB_ERR(db_create(&strings, env, 0)); + + /* Enable duplicate keys. This allows the data to be spread out across + multiple records. Note: this must occur before ->open(). */ + BDB_ERR(strings->set_flags(strings, DB_DUP)); + + BDB_ERR((strings->open)(SVN_BDB_OPEN_PARAMS(strings, NULL), + "strings", 0, DB_BTREE, + open_flags, 0666)); + + if (create) + { + DBT key, value; + + /* Create the `next-key' table entry. */ + BDB_ERR(strings->put + (strings, 0, + svn_fs_base__str_to_dbt(&key, NEXT_KEY_KEY), + svn_fs_base__str_to_dbt(&value, "0"), 0)); + } + + *strings_p = strings; + return 0; +} + + + +/*** Storing and retrieving strings. ***/ + +/* Allocate *CURSOR and advance it to first row in the set of rows + whose key is defined by QUERY. Set *LENGTH to the size of that + first row. */ +static svn_error_t * +locate_key(apr_size_t *length, + DBC **cursor, + DBT *query, + svn_fs_t *fs, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + int db_err; + DBT result; + + svn_fs_base__trail_debug(trail, "strings", "cursor"); + SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading a string"), + bfd->strings->cursor(bfd->strings, trail->db_txn, + cursor, 0))); + + /* Set up the DBT for reading the length of the record. */ + svn_fs_base__clear_dbt(&result); + result.ulen = 0; + result.flags |= DB_DBT_USERMEM; + + /* Advance the cursor to the key that we're looking for. */ + db_err = svn_bdb_dbc_get(*cursor, query, &result, DB_SET); + + /* We don't need to svn_fs_base__track_dbt() the result, because nothing + was allocated in it. */ + + /* If there's no such node, return an appropriately specific error. */ + if (db_err == DB_NOTFOUND) + { + svn_bdb_dbc_close(*cursor); + return svn_error_createf + (SVN_ERR_FS_NO_SUCH_STRING, 0, + "No such string '%s'", (const char *)query->data); + } + if (db_err) + { + DBT rerun; + + if (db_err != SVN_BDB_DB_BUFFER_SMALL) + { + svn_bdb_dbc_close(*cursor); + return BDB_WRAP(fs, N_("moving cursor"), db_err); + } + + /* We got an SVN_BDB_DB_BUFFER_SMALL (typical since we have a + zero length buf), so we need to re-run the operation to make + it happen. */ + svn_fs_base__clear_dbt(&rerun); + rerun.flags |= DB_DBT_USERMEM | DB_DBT_PARTIAL; + db_err = svn_bdb_dbc_get(*cursor, query, &rerun, DB_SET); + if (db_err) + { + svn_bdb_dbc_close(*cursor); + return BDB_WRAP(fs, N_("rerunning cursor move"), db_err); + } + } + + /* ### this cast might not be safe? */ + *length = (apr_size_t) result.size; + + return SVN_NO_ERROR; +} + + +/* Advance CURSOR by a single row in the set of rows whose keys match + CURSOR's current location. Set *LENGTH to the size of that next + row. If any error occurs, CURSOR will be destroyed. */ +static int +get_next_length(apr_size_t *length, DBC *cursor, DBT *query) +{ + DBT result; + int db_err; + + /* Set up the DBT for reading the length of the record. */ + svn_fs_base__clear_dbt(&result); + result.ulen = 0; + result.flags |= DB_DBT_USERMEM; + + /* Note: this may change the QUERY DBT, but that's okay: we're going + to be sticking with the same key anyways. */ + db_err = svn_bdb_dbc_get(cursor, query, &result, DB_NEXT_DUP); + + /* Note that we exit on DB_NOTFOUND. The caller uses that to end a loop. */ + if (db_err) + { + DBT rerun; + + if (db_err != SVN_BDB_DB_BUFFER_SMALL) + { + svn_bdb_dbc_close(cursor); + return db_err; + } + + /* We got an SVN_BDB_DB_BUFFER_SMALL (typical since we have a + zero length buf), so we need to re-run the operation to make + it happen. */ + svn_fs_base__clear_dbt(&rerun); + rerun.flags |= DB_DBT_USERMEM | DB_DBT_PARTIAL; + db_err = svn_bdb_dbc_get(cursor, query, &rerun, DB_NEXT_DUP); + if (db_err) + svn_bdb_dbc_close(cursor); + } + + /* ### this cast might not be safe? */ + *length = (apr_size_t) result.size; + return db_err; +} + + +svn_error_t * +svn_fs_bdb__string_read(svn_fs_t *fs, + const char *key, + char *buf, + svn_filesize_t offset, + apr_size_t *len, + trail_t *trail, + apr_pool_t *pool) +{ + int db_err; + DBT query, result; + DBC *cursor; + apr_size_t length, bytes_read = 0; + + svn_fs_base__str_to_dbt(&query, key); + + SVN_ERR(locate_key(&length, &cursor, &query, fs, trail, pool)); + + /* Seek through the records for this key, trying to find the record that + includes OFFSET. Note that we don't require reading from more than + one record since we're allowed to return partial reads. */ + while (length <= offset) + { + offset -= length; + + /* Remember, if any error happens, our cursor has been closed + for us. */ + db_err = get_next_length(&length, cursor, &query); + + /* No more records? They tried to read past the end. */ + if (db_err == DB_NOTFOUND) + { + *len = 0; + return SVN_NO_ERROR; + } + if (db_err) + return BDB_WRAP(fs, N_("reading string"), db_err); + } + + /* The current record contains OFFSET. Fetch the contents now. Note that + OFFSET has been moved to be relative to this record. The length could + quite easily extend past this record, so we use DB_DBT_PARTIAL and + read successive records until we've filled the request. */ + while (1) + { + svn_fs_base__clear_dbt(&result); + result.data = buf + bytes_read; + result.ulen = *len - bytes_read; + result.doff = (u_int32_t)offset; + result.dlen = *len - bytes_read; + result.flags |= (DB_DBT_USERMEM | DB_DBT_PARTIAL); + db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_CURRENT); + if (db_err) + { + svn_bdb_dbc_close(cursor); + return BDB_WRAP(fs, N_("reading string"), db_err); + } + + bytes_read += result.size; + if (bytes_read == *len) + { + /* Done with the cursor. */ + SVN_ERR(BDB_WRAP(fs, N_("closing string-reading cursor"), + svn_bdb_dbc_close(cursor))); + break; + } + + /* Remember, if any error happens, our cursor has been closed + for us. */ + db_err = get_next_length(&length, cursor, &query); + if (db_err == DB_NOTFOUND) + break; + if (db_err) + return BDB_WRAP(fs, N_("reading string"), db_err); + + /* We'll be reading from the beginning of the next record */ + offset = 0; + } + + *len = bytes_read; + return SVN_NO_ERROR; +} + + +/* Get the current 'next-key' value and bump the record. */ +static svn_error_t * +get_key_and_bump(svn_fs_t *fs, + const char **key, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + DBC *cursor; + char next_key[MAX_KEY_SIZE]; + apr_size_t key_len; + int db_err; + DBT query; + DBT result; + + /* ### todo: see issue #409 for why bumping the key as part of this + trail is problematic. */ + + /* Open a cursor and move it to the 'next-key' value. We can then fetch + the contents and use the cursor to overwrite those contents. Since + this database allows duplicates, we can't do an arbitrary 'put' to + write the new value -- that would append, not overwrite. */ + + svn_fs_base__trail_debug(trail, "strings", "cursor"); + SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading a string"), + bfd->strings->cursor(bfd->strings, trail->db_txn, + &cursor, 0))); + + /* Advance the cursor to 'next-key' and read it. */ + + db_err = svn_bdb_dbc_get(cursor, + svn_fs_base__str_to_dbt(&query, NEXT_KEY_KEY), + svn_fs_base__result_dbt(&result), + DB_SET); + if (db_err) + { + svn_bdb_dbc_close(cursor); + return BDB_WRAP(fs, N_("getting next-key value"), db_err); + } + + svn_fs_base__track_dbt(&result, pool); + *key = apr_pstrmemdup(pool, result.data, result.size); + + /* Bump to future key. */ + key_len = result.size; + svn_fs_base__next_key(result.data, &key_len, next_key); + + /* Shove the new key back into the database, at the cursor position. */ + db_err = svn_bdb_dbc_put(cursor, &query, + svn_fs_base__str_to_dbt(&result, next_key), + DB_CURRENT); + if (db_err) + { + svn_bdb_dbc_close(cursor); /* ignore the error, the original is + more important. */ + return BDB_WRAP(fs, N_("bumping next string key"), db_err); + } + + return BDB_WRAP(fs, N_("closing string-reading cursor"), + svn_bdb_dbc_close(cursor)); +} + +svn_error_t * +svn_fs_bdb__string_append(svn_fs_t *fs, + const char **key, + apr_size_t len, + const char *buf, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + DBT query, result; + + /* If the passed-in key is NULL, we graciously generate a new string + using the value of the `next-key' record in the strings table. */ + if (*key == NULL) + { + SVN_ERR(get_key_and_bump(fs, key, trail, pool)); + } + + /* Store a new record into the database. */ + svn_fs_base__trail_debug(trail, "strings", "put"); + return BDB_WRAP(fs, N_("appending string"), + bfd->strings->put + (bfd->strings, trail->db_txn, + svn_fs_base__str_to_dbt(&query, *key), + svn_fs_base__set_dbt(&result, buf, len), + 0)); +} + + +svn_error_t * +svn_fs_bdb__string_clear(svn_fs_t *fs, + const char *key, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + int db_err; + DBT query, result; + + svn_fs_base__str_to_dbt(&query, key); + + /* Torch the prior contents */ + svn_fs_base__trail_debug(trail, "strings", "del"); + db_err = bfd->strings->del(bfd->strings, trail->db_txn, &query, 0); + + /* If there's no such node, return an appropriately specific error. */ + if (db_err == DB_NOTFOUND) + return svn_error_createf + (SVN_ERR_FS_NO_SUCH_STRING, 0, + "No such string '%s'", key); + + /* Handle any other error conditions. */ + SVN_ERR(BDB_WRAP(fs, N_("clearing string"), db_err)); + + /* Shove empty data back in for this key. */ + svn_fs_base__clear_dbt(&result); + result.data = 0; + result.size = 0; + result.flags |= DB_DBT_USERMEM; + + svn_fs_base__trail_debug(trail, "strings", "put"); + return BDB_WRAP(fs, N_("storing empty contents"), + bfd->strings->put(bfd->strings, trail->db_txn, + &query, &result, 0)); +} + + +svn_error_t * +svn_fs_bdb__string_size(svn_filesize_t *size, + svn_fs_t *fs, + const char *key, + trail_t *trail, + apr_pool_t *pool) +{ + int db_err; + DBT query; + DBC *cursor; + apr_size_t length; + svn_filesize_t total; + + svn_fs_base__str_to_dbt(&query, key); + + SVN_ERR(locate_key(&length, &cursor, &query, fs, trail, pool)); + + total = length; + while (1) + { + /* Remember, if any error happens, our cursor has been closed + for us. */ + db_err = get_next_length(&length, cursor, &query); + + /* No more records? Then return the total length. */ + if (db_err == DB_NOTFOUND) + { + *size = total; + return SVN_NO_ERROR; + } + if (db_err) + return BDB_WRAP(fs, N_("fetching string length"), db_err); + + total += length; + } + + /* NOTREACHED */ +} + + +svn_error_t * +svn_fs_bdb__string_delete(svn_fs_t *fs, + const char *key, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + int db_err; + DBT query; + + svn_fs_base__trail_debug(trail, "strings", "del"); + db_err = bfd->strings->del(bfd->strings, trail->db_txn, + svn_fs_base__str_to_dbt(&query, key), 0); + + /* If there's no such node, return an appropriately specific error. */ + if (db_err == DB_NOTFOUND) + return svn_error_createf + (SVN_ERR_FS_NO_SUCH_STRING, 0, + "No such string '%s'", key); + + /* Handle any other error conditions. */ + return BDB_WRAP(fs, N_("deleting string"), db_err); +} + + +svn_error_t * +svn_fs_bdb__string_copy(svn_fs_t *fs, + const char **new_key, + const char *key, + trail_t *trail, + apr_pool_t *pool) +{ + base_fs_data_t *bfd = fs->fsap_data; + DBT query; + DBT result; + DBT copykey; + DBC *cursor; + int db_err; + + /* Copy off the old key in case the caller is sharing storage + between the old and new keys. */ + const char *old_key = apr_pstrdup(pool, key); + + SVN_ERR(get_key_and_bump(fs, new_key, trail, pool)); + + svn_fs_base__trail_debug(trail, "strings", "cursor"); + SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading a string"), + bfd->strings->cursor(bfd->strings, trail->db_txn, + &cursor, 0))); + + svn_fs_base__str_to_dbt(&query, old_key); + svn_fs_base__str_to_dbt(©key, *new_key); + + svn_fs_base__clear_dbt(&result); + + /* Move to the first record and fetch its data (under BDB's mem mgmt). */ + db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_SET); + if (db_err) + { + svn_bdb_dbc_close(cursor); + return BDB_WRAP(fs, N_("getting next-key value"), db_err); + } + + while (1) + { + /* ### can we pass a BDB-provided buffer to another BDB function? + ### they are supposed to have a duration up to certain points + ### of calling back into BDB, but I'm not sure what the exact + ### rules are. it is definitely nicer to use BDB buffers here + ### to simplify things and reduce copies, but... hrm. + */ + + /* Write the data to the database */ + svn_fs_base__trail_debug(trail, "strings", "put"); + db_err = bfd->strings->put(bfd->strings, trail->db_txn, + ©key, &result, 0); + if (db_err) + { + svn_bdb_dbc_close(cursor); + return BDB_WRAP(fs, N_("writing copied data"), db_err); + } + + /* Read the next chunk. Terminate loop if we're done. */ + svn_fs_base__clear_dbt(&result); + db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_NEXT_DUP); + if (db_err == DB_NOTFOUND) + break; + if (db_err) + { + svn_bdb_dbc_close(cursor); + return BDB_WRAP(fs, N_("fetching string data for a copy"), db_err); + } + } + + return BDB_WRAP(fs, N_("closing string-reading cursor"), + svn_bdb_dbc_close(cursor)); +} |