| /* strings-table.c : operations on the `strings' table |
| * |
| * ==================================================================== |
| * Copyright (c) 2000-2002 CollabNet. All rights reserved. |
| * |
| * This software is licensed as described in the file COPYING, which |
| * you should have received as part of this distribution. The terms |
| * are also available at http://subversion.tigris.org/license-1.html. |
| * If newer versions of this license are posted there, you may use a |
| * newer version instead, at your option. |
| * |
| * This software consists of voluntary contributions made by many |
| * individuals. For exact contribution history, see the revision |
| * history and logs, available at http://subversion.tigris.org/. |
| * ==================================================================== |
| */ |
| |
| #include "db.h" |
| #include "svn_fs.h" |
| #include "fs.h" |
| #include "err.h" |
| #include "dbt.h" |
| #include "trail.h" |
| #include "strings-table.h" |
| #include "key-gen.h" |
| #include "svn_pools.h" |
| |
| |
| /*** Creating and opening the strings table. ***/ |
| |
| int |
| svn_fs__open_strings_table (DB **strings_p, |
| DB_ENV *env, |
| int create) |
| { |
| DB *strings; |
| |
| DB_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(). */ |
| DB_ERR (strings->set_flags (strings, DB_DUP)); |
| |
| DB_ERR (strings->open (strings, "strings", 0, DB_BTREE, |
| create ? (DB_CREATE | DB_EXCL) : 0, |
| 0666)); |
| |
| if (create) |
| { |
| DBT key, value; |
| |
| /* Create the `next-key' table entry. */ |
| DB_ERR (strings->put |
| (strings, 0, |
| svn_fs__str_to_dbt (&key, (char *) svn_fs__next_key_key), |
| svn_fs__str_to_dbt (&value, (char *) "0"), |
| 0)); |
| } |
| |
| *strings_p = strings; |
| return 0; |
| } |
| |
| |
| |
| /*** Storing and retrieving strings. ***/ |
| |
| static svn_error_t * |
| locate_key (apr_size_t *length, |
| DBC **cursor, |
| DBT *query, |
| svn_fs_t *fs, |
| trail_t *trail) |
| { |
| int db_err; |
| DBT result; |
| |
| SVN_ERR (DB_WRAP (fs, "creating cursor for reading a string", |
| fs->strings->cursor (fs->strings, trail->db_txn, |
| cursor, 0))); |
| |
| /* Set up the DBT for reading the length of the record. */ |
| svn_fs__clear_dbt (&result); |
| result.ulen = 0; |
| result.flags |= DB_DBT_USERMEM; |
| |
| /* Advance the cursor to the key that we're looking for. */ |
| db_err = (*cursor)->c_get (*cursor, query, &result, DB_SET); |
| |
| /* We don't need to svn_fs__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) |
| { |
| (*cursor)->c_close (*cursor); |
| return svn_error_createf |
| (SVN_ERR_FS_NO_SUCH_STRING, 0, 0, fs->pool, |
| "locate_key: no such string `%s'", (const char *)query->data); |
| } |
| if (db_err) |
| { |
| DBT rerun; |
| |
| if (db_err != ENOMEM) |
| { |
| (*cursor)->c_close (*cursor); |
| return DB_WRAP (fs, "could not move cursor", db_err); |
| } |
| |
| /* We got an ENOMEM (typical since we have a zero length buf), so |
| we need to re-run the operation to make it happen. */ |
| svn_fs__clear_dbt (&rerun); |
| rerun.flags |= DB_DBT_USERMEM | DB_DBT_PARTIAL; |
| SVN_ERR (DB_WRAP (fs, "rerunning cursor move", |
| (*cursor)->c_get (*cursor, query, &rerun, DB_SET))); |
| } |
| |
| /* ### this cast might not be safe? */ |
| *length = (apr_size_t) result.size; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| 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__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 = cursor->c_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 != ENOMEM) |
| { |
| cursor->c_close (cursor); |
| return db_err; |
| } |
| |
| /* We got an ENOMEM (typical since we have a zero length buf), so |
| we need to re-run the operation to make it happen. */ |
| svn_fs__clear_dbt (&rerun); |
| rerun.flags |= DB_DBT_USERMEM | DB_DBT_PARTIAL; |
| db_err = cursor->c_get (cursor, query, &rerun, DB_NEXT_DUP); |
| } |
| |
| /* ### this cast might not be safe? */ |
| *length = (apr_size_t) result.size; |
| return db_err; |
| } |
| |
| |
| /* Read *LEN bytes into BUF from OFFSET in string KEY in FS, as part |
| * of TRAIL. |
| * |
| * On return, *LEN is set to the number of bytes read. This value may |
| * be less than the number requested. |
| * |
| * If OFFSET is past the end of the string, then *LEN will be set to |
| * zero. Callers which are advancing OFFSET as they read portions of |
| * the string can terminate their loop when *LEN is returned as zero |
| * (which will occur when OFFSET == length(the string)). |
| * |
| * If string KEY does not exist, the error SVN_ERR_FS_NO_SUCH_STRING |
| * is returned. |
| */ |
| static svn_error_t * |
| string_read (svn_fs_t *fs, |
| const char *key, |
| char *buf, |
| apr_off_t offset, |
| apr_size_t *len, |
| trail_t *trail) |
| { |
| int db_err; |
| DBT query, result; |
| DBC *cursor; |
| apr_size_t length; |
| |
| svn_fs__str_to_dbt (&query, (char *) key); |
| |
| SVN_ERR (locate_key (&length, &cursor, &query, fs, trail)); |
| |
| /* 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; |
| |
| 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 DB_WRAP (fs, "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, but no big deal. We also keep |
| the DB_DBT_PARTIAL to read little pieces at this location. */ |
| svn_fs__clear_dbt (&result); |
| result.data = buf; |
| result.ulen = *len; |
| result.doff = offset; |
| result.dlen = *len; |
| result.flags |= (DB_DBT_USERMEM | DB_DBT_PARTIAL); |
| db_err = cursor->c_get (cursor, &query, &result, DB_CURRENT); |
| if (db_err) |
| goto cursor_error; |
| |
| /* Done with the cursor. */ |
| SVN_ERR (DB_WRAP (fs, "closing string-reading cursor", |
| cursor->c_close (cursor))); |
| |
| *len = result.size; |
| return SVN_NO_ERROR; |
| |
| cursor_error: |
| /* An error occurred somewhere. Close the cursor and return the error. */ |
| cursor->c_close (cursor); |
| return DB_WRAP (fs, "reading string", db_err); |
| } |
| |
| |
| svn_error_t * |
| svn_fs__string_read (svn_fs_t *fs, |
| const char *key, |
| char *buf, |
| apr_off_t offset, |
| apr_size_t *len, |
| trail_t *trail) |
| { |
| apr_size_t amt_read = 0; |
| |
| /* ### todo: See IZ issue #642. |
| |
| Currently, the helper function for this, string_read() is having |
| to lookup keys and setup the cursor on each call. Ideally, the |
| following looping concept would be inside that helper so would |
| only have to setup the cursor once. */ |
| while (1) |
| { |
| apr_size_t size = *len - amt_read; |
| SVN_ERR (string_read (fs, key, buf + amt_read, offset + amt_read, |
| &size, trail)); |
| amt_read += size; |
| if ((size == 0) || (amt_read == *len)) |
| break; |
| } |
| |
| *len = amt_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) |
| { |
| DBC *cursor; |
| char next_key[200]; |
| 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_ERR (DB_WRAP (fs, "creating cursor for reading a string", |
| fs->strings->cursor (fs->strings, trail->db_txn, |
| &cursor, 0))); |
| |
| /* Advance the cursor to 'next-key' and read it. */ |
| |
| db_err = cursor->c_get (cursor, |
| svn_fs__str_to_dbt (&query, |
| (char *) svn_fs__next_key_key), |
| svn_fs__result_dbt (&result), |
| DB_SET); |
| if (db_err) |
| { |
| cursor->c_close (cursor); |
| return DB_WRAP (fs, "getting next-key value", db_err); |
| } |
| |
| svn_fs__track_dbt (&result, trail->pool); |
| *key = apr_pstrndup (trail->pool, result.data, result.size); |
| |
| /* Bump to future key. */ |
| key_len = result.size; |
| svn_fs__next_key (result.data, &key_len, next_key); |
| |
| /* Shove the new key back into the database, at the cursor position. */ |
| db_err = cursor->c_put (cursor, &query, |
| svn_fs__str_to_dbt (&result, (char *) next_key), |
| DB_CURRENT); |
| |
| cursor->c_close (cursor); |
| |
| return DB_WRAP (fs, "bumping next string key", db_err); |
| } |
| |
| svn_error_t * |
| svn_fs__string_append (svn_fs_t *fs, |
| const char **key, |
| apr_size_t len, |
| const char *buf, |
| trail_t *trail) |
| { |
| 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)); |
| } |
| |
| /* Store a new record into the database. */ |
| SVN_ERR (DB_WRAP (fs, "appending string", |
| fs->strings->put |
| (fs->strings, trail->db_txn, |
| svn_fs__str_to_dbt (&query, (char *) (*key)), |
| svn_fs__set_dbt (&result, (void *) buf, len), |
| 0))); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t * |
| svn_fs__string_clear (svn_fs_t *fs, |
| const char *key, |
| trail_t *trail) |
| { |
| int db_err; |
| DBT query, result; |
| |
| svn_fs__str_to_dbt (&query, (char *)key); |
| |
| /* Torch the prior contents */ |
| db_err = fs->strings->del (fs->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, 0, fs->pool, |
| "svn_fs__string_clear: no such string `%s'", key); |
| |
| /* Handle any other error conditions. */ |
| SVN_ERR (DB_WRAP (fs, "clearing string", db_err)); |
| |
| /* Shove empty data back in for this key. */ |
| svn_fs__clear_dbt (&result); |
| result.data = 0; |
| result.size = 0; |
| result.flags |= DB_DBT_USERMEM; |
| |
| return DB_WRAP (fs, "storing empty contents", |
| fs->strings->put (fs->strings, trail->db_txn, |
| &query, &result, 0)); |
| } |
| |
| |
| svn_error_t * |
| svn_fs__string_size (apr_size_t *size, |
| svn_fs_t *fs, |
| const char *key, |
| trail_t *trail) |
| { |
| int db_err; |
| DBT query; |
| DBC *cursor; |
| apr_size_t length; |
| apr_size_t total; |
| |
| svn_fs__str_to_dbt (&query, (char *) key); |
| |
| SVN_ERR (locate_key (&length, &cursor, &query, fs, trail)); |
| |
| total = length; |
| while (1) |
| { |
| 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 DB_WRAP (fs, "fetching string length", db_err); |
| |
| total += length; |
| } |
| |
| /* NOTREACHED */ |
| } |
| |
| |
| svn_error_t * |
| svn_fs__string_delete (svn_fs_t *fs, |
| const char *key, |
| trail_t *trail) |
| { |
| int db_err; |
| DBT query; |
| |
| db_err = fs->strings->del (fs->strings, trail->db_txn, |
| svn_fs__str_to_dbt (&query, (char *) 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, 0, fs->pool, |
| "svn_fs__delete_string: no such string `%s'", key); |
| |
| /* Handle any other error conditions. */ |
| SVN_ERR (DB_WRAP (fs, "deleting string", db_err)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t * |
| svn_fs__string_copy (svn_fs_t *fs, |
| const char **new_key, |
| const char *key, |
| trail_t *trail) |
| { |
| DBT query; |
| DBT result; |
| DBT copykey; |
| DBC *cursor; |
| int db_err; |
| |
| SVN_ERR (get_key_and_bump (fs, new_key, trail)); |
| |
| SVN_ERR (DB_WRAP (fs, "creating cursor for reading a string", |
| fs->strings->cursor (fs->strings, trail->db_txn, |
| &cursor, 0))); |
| |
| svn_fs__str_to_dbt (&query, (char *) key); |
| svn_fs__str_to_dbt (©key, (char *) *new_key); |
| |
| svn_fs__clear_dbt (&result); |
| |
| /* Move to the first record and fetch its data (under BDB's mem mgmt). */ |
| db_err = cursor->c_get (cursor, &query, &result, DB_SET); |
| if (db_err) |
| { |
| cursor->c_close (cursor); |
| return DB_WRAP (fs, "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 */ |
| db_err = fs->strings->put (fs->strings, trail->db_txn, |
| ©key, &result, 0); |
| if (db_err) |
| { |
| cursor->c_close (cursor); |
| return DB_WRAP (fs, "writing copied data", db_err); |
| } |
| |
| /* Read the next chunk. Terminate loop if we're done. */ |
| svn_fs__clear_dbt (&result); |
| db_err = cursor->c_get (cursor, &query, &result, DB_NEXT_DUP); |
| if (db_err == DB_NOTFOUND) |
| break; |
| if (db_err) |
| { |
| cursor->c_close (cursor); |
| return DB_WRAP (fs, "fetching string data for a copy", db_err); |
| } |
| } |
| |
| cursor->c_close (cursor); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| |
| /* |
| * local variables: |
| * eval: (load-file "../../tools/dev/svn-dev.el") |
| * end: |
| */ |