1/* txn-table.c : operations on the `transactions' 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 <string.h> 24#include <assert.h> 25 26#include "bdb_compat.h" 27 28#include "svn_pools.h" 29#include "private/svn_skel.h" 30 31#include "dbt.h" 32#include "../err.h" 33#include "../fs.h" 34#include "../key-gen.h" 35#include "../util/fs_skels.h" 36#include "../trail.h" 37#include "../../libsvn_fs/fs-loader.h" 38#include "bdb-err.h" 39#include "txn-table.h" 40 41#include "svn_private_config.h" 42 43 44static svn_boolean_t 45is_committed(transaction_t *txn) 46{ 47 return (txn->kind == transaction_kind_committed); 48} 49 50 51int 52svn_fs_bdb__open_transactions_table(DB **transactions_p, 53 DB_ENV *env, 54 svn_boolean_t create) 55{ 56 const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0); 57 DB *txns; 58 59 BDB_ERR(svn_fs_bdb__check_version()); 60 BDB_ERR(db_create(&txns, env, 0)); 61 BDB_ERR((txns->open)(SVN_BDB_OPEN_PARAMS(txns, NULL), 62 "transactions", 0, DB_BTREE, 63 open_flags, 0666)); 64 65 /* Create the `next-key' table entry. */ 66 if (create) 67 { 68 DBT key, value; 69 70 BDB_ERR(txns->put(txns, 0, 71 svn_fs_base__str_to_dbt(&key, NEXT_KEY_KEY), 72 svn_fs_base__str_to_dbt(&value, "0"), 0)); 73 } 74 75 *transactions_p = txns; 76 return 0; 77} 78 79 80svn_error_t * 81svn_fs_bdb__put_txn(svn_fs_t *fs, 82 const transaction_t *txn, 83 const char *txn_name, 84 trail_t *trail, 85 apr_pool_t *pool) 86{ 87 base_fs_data_t *bfd = fs->fsap_data; 88 svn_skel_t *txn_skel; 89 DBT key, value; 90 91 /* Convert native type to skel. */ 92 SVN_ERR(svn_fs_base__unparse_transaction_skel(&txn_skel, txn, pool)); 93 94 /* Only in the context of this function do we know that the DB call 95 will not attempt to modify txn_name, so the cast belongs here. */ 96 svn_fs_base__str_to_dbt(&key, txn_name); 97 svn_fs_base__skel_to_dbt(&value, txn_skel, pool); 98 svn_fs_base__trail_debug(trail, "transactions", "put"); 99 return BDB_WRAP(fs, N_("storing transaction record"), 100 bfd->transactions->put(bfd->transactions, trail->db_txn, 101 &key, &value, 0)); 102} 103 104 105/* Allocate a Subversion transaction ID in FS, as part of TRAIL. Set 106 *ID_P to the new transaction ID, allocated in POOL. */ 107static svn_error_t * 108allocate_txn_id(const char **id_p, 109 svn_fs_t *fs, 110 trail_t *trail, 111 apr_pool_t *pool) 112{ 113 base_fs_data_t *bfd = fs->fsap_data; 114 DBT query, result; 115 apr_size_t len; 116 char next_key[MAX_KEY_SIZE]; 117 int db_err; 118 119 svn_fs_base__str_to_dbt(&query, NEXT_KEY_KEY); 120 121 /* Get the current value associated with the `next-key' key in the table. */ 122 svn_fs_base__trail_debug(trail, "transactions", "get"); 123 SVN_ERR(BDB_WRAP(fs, N_("allocating new transaction ID (getting 'next-key')"), 124 bfd->transactions->get(bfd->transactions, trail->db_txn, 125 &query, 126 svn_fs_base__result_dbt(&result), 127 0))); 128 svn_fs_base__track_dbt(&result, pool); 129 130 /* Set our return value. */ 131 *id_p = apr_pstrmemdup(pool, result.data, result.size); 132 133 /* Bump to future key. */ 134 len = result.size; 135 svn_fs_base__next_key(result.data, &len, next_key); 136 svn_fs_base__str_to_dbt(&query, NEXT_KEY_KEY); 137 svn_fs_base__str_to_dbt(&result, next_key); 138 svn_fs_base__trail_debug(trail, "transactions", "put"); 139 db_err = bfd->transactions->put(bfd->transactions, trail->db_txn, 140 &query, &result, 0); 141 142 return BDB_WRAP(fs, N_("bumping next transaction key"), db_err); 143} 144 145 146svn_error_t * 147svn_fs_bdb__create_txn(const char **txn_name_p, 148 svn_fs_t *fs, 149 const svn_fs_id_t *root_id, 150 trail_t *trail, 151 apr_pool_t *pool) 152{ 153 const char *txn_name; 154 transaction_t txn; 155 156 SVN_ERR(allocate_txn_id(&txn_name, fs, trail, pool)); 157 txn.kind = transaction_kind_normal; 158 txn.root_id = root_id; 159 txn.base_id = root_id; 160 txn.proplist = NULL; 161 txn.copies = NULL; 162 txn.revision = SVN_INVALID_REVNUM; 163 SVN_ERR(svn_fs_bdb__put_txn(fs, &txn, txn_name, trail, pool)); 164 165 *txn_name_p = txn_name; 166 return SVN_NO_ERROR; 167} 168 169 170svn_error_t * 171svn_fs_bdb__delete_txn(svn_fs_t *fs, 172 const char *txn_name, 173 trail_t *trail, 174 apr_pool_t *pool) 175{ 176 base_fs_data_t *bfd = fs->fsap_data; 177 DBT key; 178 transaction_t *txn; 179 180 /* Make sure TXN is dead. */ 181 SVN_ERR(svn_fs_bdb__get_txn(&txn, fs, txn_name, trail, pool)); 182 if (is_committed(txn)) 183 return svn_fs_base__err_txn_not_mutable(fs, txn_name); 184 185 /* Delete the transaction from the `transactions' table. */ 186 svn_fs_base__str_to_dbt(&key, txn_name); 187 svn_fs_base__trail_debug(trail, "transactions", "del"); 188 return BDB_WRAP(fs, N_("deleting entry from 'transactions' table"), 189 bfd->transactions->del(bfd->transactions, 190 trail->db_txn, &key, 0)); 191} 192 193 194svn_error_t * 195svn_fs_bdb__get_txn(transaction_t **txn_p, 196 svn_fs_t *fs, 197 const char *txn_name, 198 trail_t *trail, 199 apr_pool_t *pool) 200{ 201 base_fs_data_t *bfd = fs->fsap_data; 202 DBT key, value; 203 int db_err; 204 svn_skel_t *skel; 205 transaction_t *transaction; 206 207 /* Only in the context of this function do we know that the DB call 208 will not attempt to modify txn_name, so the cast belongs here. */ 209 svn_fs_base__trail_debug(trail, "transactions", "get"); 210 db_err = bfd->transactions->get(bfd->transactions, trail->db_txn, 211 svn_fs_base__str_to_dbt(&key, txn_name), 212 svn_fs_base__result_dbt(&value), 213 0); 214 svn_fs_base__track_dbt(&value, pool); 215 216 if (db_err == DB_NOTFOUND) 217 return svn_fs_base__err_no_such_txn(fs, txn_name); 218 SVN_ERR(BDB_WRAP(fs, N_("reading transaction"), db_err)); 219 220 /* Parse TRANSACTION skel */ 221 skel = svn_skel__parse(value.data, value.size, pool); 222 if (! skel) 223 return svn_fs_base__err_corrupt_txn(fs, txn_name); 224 225 /* Convert skel to native type. */ 226 SVN_ERR(svn_fs_base__parse_transaction_skel(&transaction, skel, pool)); 227 *txn_p = transaction; 228 return SVN_NO_ERROR; 229} 230 231 232svn_error_t * 233svn_fs_bdb__get_txn_list(apr_array_header_t **names_p, 234 svn_fs_t *fs, 235 trail_t *trail, 236 apr_pool_t *pool) 237{ 238 base_fs_data_t *bfd = fs->fsap_data; 239 apr_size_t const next_key_key_len = strlen(NEXT_KEY_KEY); 240 apr_pool_t *subpool = svn_pool_create(pool); 241 apr_array_header_t *names; 242 DBC *cursor; 243 DBT key, value; 244 int db_err, db_c_err; 245 246 /* Allocate the initial names array */ 247 names = apr_array_make(pool, 4, sizeof(const char *)); 248 249 /* Create a database cursor to list the transaction names. */ 250 svn_fs_base__trail_debug(trail, "transactions", "cursor"); 251 SVN_ERR(BDB_WRAP(fs, N_("reading transaction list (opening cursor)"), 252 bfd->transactions->cursor(bfd->transactions, 253 trail->db_txn, &cursor, 0))); 254 255 /* Build a null-terminated array of keys in the transactions table. */ 256 for (db_err = svn_bdb_dbc_get(cursor, 257 svn_fs_base__result_dbt(&key), 258 svn_fs_base__result_dbt(&value), 259 DB_FIRST); 260 db_err == 0; 261 db_err = svn_bdb_dbc_get(cursor, 262 svn_fs_base__result_dbt(&key), 263 svn_fs_base__result_dbt(&value), 264 DB_NEXT)) 265 { 266 transaction_t *txn; 267 svn_skel_t *txn_skel; 268 svn_error_t *err; 269 270 /* Clear the per-iteration subpool */ 271 svn_pool_clear(subpool); 272 273 /* Track the memory alloc'd for fetching the key and value here 274 so that when the containing pool is cleared, this memory is 275 freed. */ 276 svn_fs_base__track_dbt(&key, subpool); 277 svn_fs_base__track_dbt(&value, subpool); 278 279 /* Ignore the "next-key" key. */ 280 if (key.size == next_key_key_len 281 && 0 == memcmp(key.data, NEXT_KEY_KEY, next_key_key_len)) 282 continue; 283 284 /* Parse TRANSACTION skel */ 285 txn_skel = svn_skel__parse(value.data, value.size, subpool); 286 if (! txn_skel) 287 { 288 svn_bdb_dbc_close(cursor); 289 return svn_fs_base__err_corrupt_txn 290 (fs, apr_pstrmemdup(pool, key.data, key.size)); 291 } 292 293 /* Convert skel to native type. */ 294 if ((err = svn_fs_base__parse_transaction_skel(&txn, txn_skel, 295 subpool))) 296 { 297 svn_bdb_dbc_close(cursor); 298 return svn_error_trace(err); 299 } 300 301 /* If this is an immutable "committed" transaction, ignore it. */ 302 if (is_committed(txn)) 303 continue; 304 305 /* Add the transaction name to the NAMES array, duping it into POOL. */ 306 APR_ARRAY_PUSH(names, const char *) = apr_pstrmemdup(pool, key.data, 307 key.size); 308 } 309 310 /* Check for errors, but close the cursor first. */ 311 db_c_err = svn_bdb_dbc_close(cursor); 312 if (db_err != DB_NOTFOUND) 313 { 314 SVN_ERR(BDB_WRAP(fs, N_("reading transaction list (listing keys)"), 315 db_err)); 316 } 317 SVN_ERR(BDB_WRAP(fs, N_("reading transaction list (closing cursor)"), 318 db_c_err)); 319 320 /* Destroy the per-iteration subpool */ 321 svn_pool_destroy(subpool); 322 323 *names_p = names; 324 return SVN_NO_ERROR; 325} 326