1/*- 2 * See the file LICENSE for redistribution information. 3 * 4 * Copyright (c) 2001, 2010 Oracle and/or its affiliates. All rights reserved. 5 * 6 * $Id$ 7 */ 8 9#include "db_config.h" 10 11#include "db_int.h" 12#include "dbinc/db_page.h" 13#include "dbinc/fop.h" 14#include "dbinc/btree.h" 15#include "dbinc/hash.h" 16#include "dbinc/lock.h" 17#include "dbinc/mp.h" 18#include "dbinc/txn.h" 19 20static int __db_dbtxn_remove __P((DB *, 21 DB_THREAD_INFO *, DB_TXN *, const char *, const char *)); 22static int __db_subdb_remove __P((DB *, 23 DB_THREAD_INFO *, DB_TXN *, const char *, const char *)); 24 25/* 26 * __env_dbremove_pp 27 * ENV->dbremove pre/post processing. 28 * 29 * PUBLIC: int __env_dbremove_pp __P((DB_ENV *, 30 * PUBLIC: DB_TXN *, const char *, const char *, u_int32_t)); 31 */ 32int 33__env_dbremove_pp(dbenv, txn, name, subdb, flags) 34 DB_ENV *dbenv; 35 DB_TXN *txn; 36 const char *name, *subdb; 37 u_int32_t flags; 38{ 39 DB *dbp; 40 DB_THREAD_INFO *ip; 41 ENV *env; 42 int handle_check, ret, t_ret, txn_local; 43 44 dbp = NULL; 45 env = dbenv->env; 46 txn_local = 0; 47 48 ENV_ILLEGAL_BEFORE_OPEN(env, "DB_ENV->dbremove"); 49 50 /* 51 * The actual argument checking is simple, do it inline, outside of 52 * the replication block. 53 */ 54 if ((ret = __db_fchk(env, 55 "DB->remove", flags, DB_AUTO_COMMIT | DB_TXN_NOT_DURABLE)) != 0) 56 return (ret); 57 58 ENV_ENTER(env, ip); 59 60 /* Check for replication block. */ 61 handle_check = IS_ENV_REPLICATED(env); 62 if (handle_check && (ret = __env_rep_enter(env, 1)) != 0) { 63 handle_check = 0; 64 goto err; 65 } 66 67 /* 68 * Create local transaction as necessary, check for consistent 69 * transaction usage. 70 */ 71 if (IS_ENV_AUTO_COMMIT(env, txn, flags)) { 72 if ((ret = __db_txn_auto_init(env, ip, &txn)) != 0) 73 goto err; 74 txn_local = 1; 75 } else 76 if (txn != NULL && !TXN_ON(env) && 77 (!CDB_LOCKING(env) || !F_ISSET(txn, TXN_CDSGROUP))) { 78 ret = __db_not_txn_env(env); 79 goto err; 80 } 81 LF_CLR(DB_AUTO_COMMIT); 82 83 if ((ret = __db_create_internal(&dbp, env, 0)) != 0) 84 goto err; 85 if (LF_ISSET(DB_TXN_NOT_DURABLE) && 86 (ret = __db_set_flags(dbp, DB_TXN_NOT_DURABLE)) != 0) 87 goto err; 88 LF_CLR(DB_TXN_NOT_DURABLE); 89 90 ret = __db_remove_int(dbp, ip, txn, name, subdb, flags); 91 92 if (txn_local) { 93 /* 94 * We created the DBP here and when we commit/abort, we'll 95 * release all the transactional locks, including the handle 96 * lock; mark the handle cleared explicitly. 97 */ 98 LOCK_INIT(dbp->handle_lock); 99 dbp->locker = NULL; 100 } else if (txn != NULL) { 101 /* 102 * We created this handle locally so we need to close it 103 * and clean it up. Unfortunately, it's holding transactional 104 * locks that need to persist until the end of transaction. 105 * If we invalidate the locker id (dbp->locker), then the close 106 * won't free these locks prematurely. 107 */ 108 dbp->locker = NULL; 109 } 110 111err: if (txn_local && (t_ret = 112 __db_txn_auto_resolve(env, txn, 0, ret)) != 0 && ret == 0) 113 ret = t_ret; 114 115 /* 116 * We never opened this dbp for real, so don't include a transaction 117 * handle, and use NOSYNC to avoid calling into mpool. 118 * 119 * !!! 120 * Note we're reversing the order of operations: we started the txn and 121 * then opened the DB handle; we're resolving the txn and then closing 122 * closing the DB handle -- a DB handle cannot be closed before 123 * resolving the txn. 124 */ 125 if (dbp != NULL && 126 (t_ret = __db_close(dbp, NULL, DB_NOSYNC)) != 0 && ret == 0) 127 ret = t_ret; 128 129 if (handle_check && (t_ret = __env_db_rep_exit(env)) != 0 && ret == 0) 130 ret = t_ret; 131 132 ENV_LEAVE(env, ip); 133 return (ret); 134} 135 136/* 137 * __db_remove_pp 138 * DB->remove pre/post processing. 139 * 140 * PUBLIC: int __db_remove_pp 141 * PUBLIC: __P((DB *, const char *, const char *, u_int32_t)); 142 */ 143int 144__db_remove_pp(dbp, name, subdb, flags) 145 DB *dbp; 146 const char *name, *subdb; 147 u_int32_t flags; 148{ 149 DB_THREAD_INFO *ip; 150 ENV *env; 151 int handle_check, ret, t_ret; 152 153 env = dbp->env; 154 155 /* 156 * Validate arguments, continuing to destroy the handle on failure. 157 * 158 * Cannot use DB_ILLEGAL_AFTER_OPEN directly because it returns. 159 * 160 * !!! 161 * We have a serious problem if we're here with a handle used to open 162 * a database -- we'll destroy the handle, and the application won't 163 * ever be able to close the database. 164 */ 165 if (F_ISSET(dbp, DB_AM_OPEN_CALLED)) 166 return (__db_mi_open(env, "DB->remove", 1)); 167 168 /* Validate arguments. */ 169 if ((ret = __db_fchk(env, "DB->remove", flags, 0)) != 0) 170 return (ret); 171 172 /* Check for consistent transaction usage. */ 173 if ((ret = __db_check_txn(dbp, NULL, DB_LOCK_INVALIDID, 0)) != 0) 174 return (ret); 175 176 ENV_ENTER(env, ip); 177 178 handle_check = IS_ENV_REPLICATED(env); 179 if (handle_check && (ret = __db_rep_enter(dbp, 1, 1, 0)) != 0) { 180 handle_check = 0; 181 goto err; 182 } 183 184 /* Remove the file. */ 185 ret = __db_remove(dbp, ip, NULL, name, subdb, flags); 186 187 if (handle_check && (t_ret = __env_db_rep_exit(env)) != 0 && ret == 0) 188 ret = t_ret; 189 190err: ENV_LEAVE(env, ip); 191 return (ret); 192} 193 194/* 195 * __db_remove 196 * DB->remove method. 197 * 198 * PUBLIC: int __db_remove __P((DB *, DB_THREAD_INFO *, 199 * PUBLIC: DB_TXN *, const char *, const char *, u_int32_t)); 200 */ 201int 202__db_remove(dbp, ip, txn, name, subdb, flags) 203 DB *dbp; 204 DB_THREAD_INFO *ip; 205 DB_TXN *txn; 206 const char *name, *subdb; 207 u_int32_t flags; 208{ 209 int ret, t_ret; 210 211 ret = __db_remove_int(dbp, ip, txn, name, subdb, flags); 212 213 if ((t_ret = __db_close(dbp, txn, DB_NOSYNC)) != 0 && ret == 0) 214 ret = t_ret; 215 216 return (ret); 217} 218 219/* 220 * __db_remove_int 221 * Worker function for the DB->remove method. 222 * 223 * PUBLIC: int __db_remove_int __P((DB *, DB_THREAD_INFO *, 224 * PUBLIC: DB_TXN *, const char *, const char *, u_int32_t)); 225 */ 226int 227__db_remove_int(dbp, ip, txn, name, subdb, flags) 228 DB *dbp; 229 DB_THREAD_INFO *ip; 230 DB_TXN *txn; 231 const char *name, *subdb; 232 u_int32_t flags; 233{ 234 ENV *env; 235 int ret; 236 char *real_name, *tmpname; 237 238 env = dbp->env; 239 real_name = tmpname = NULL; 240 241 if (name == NULL && subdb == NULL) { 242 __db_errx(env, "Remove on temporary files invalid"); 243 ret = EINVAL; 244 goto err; 245 } 246 247 if (name == NULL) { 248 MAKE_INMEM(dbp); 249 real_name = (char *)subdb; 250 } else if (subdb != NULL) { 251 ret = __db_subdb_remove(dbp, ip, txn, name, subdb); 252 goto err; 253 } 254 255 /* Handle transactional file removes separately. */ 256 if (IS_REAL_TXN(txn)) { 257 ret = __db_dbtxn_remove(dbp, ip, txn, name, subdb); 258 goto err; 259 } 260 261 /* 262 * The remaining case is a non-transactional file remove. 263 * 264 * Find the real name of the file. 265 */ 266 if (!F_ISSET(dbp, DB_AM_INMEM) && (ret = __db_appname(env, 267 DB_APP_DATA, name, &dbp->dirname, &real_name)) != 0) 268 goto err; 269 270 /* 271 * If this is a file and force is set, remove the temporary file, which 272 * may have been left around. Ignore errors because the temporary file 273 * might not exist. 274 */ 275 if (!F_ISSET(dbp, DB_AM_INMEM) && LF_ISSET(DB_FORCE) && 276 (ret = __db_backup_name(env, real_name, NULL, &tmpname)) == 0) 277 (void)__os_unlink(env, tmpname, 0); 278 279 if ((ret = __fop_remove_setup(dbp, NULL, real_name, 0)) != 0) 280 goto err; 281 282 if (dbp->db_am_remove != NULL && 283 (ret = dbp->db_am_remove(dbp, ip, NULL, name, subdb, flags)) != 0) 284 goto err; 285 286 ret = F_ISSET(dbp, DB_AM_INMEM) ? 287 __db_inmem_remove(dbp, NULL, real_name) : 288 __fop_remove(env, 289 NULL, dbp->fileid, name, &dbp->dirname, DB_APP_DATA, 290 F_ISSET(dbp, DB_AM_NOT_DURABLE) ? DB_LOG_NOT_DURABLE : 0); 291 292err: if (!F_ISSET(dbp, DB_AM_INMEM) && real_name != NULL) 293 __os_free(env, real_name); 294 if (tmpname != NULL) 295 __os_free(env, tmpname); 296 297 return (ret); 298} 299 300/* 301 * __db_inmem_remove -- 302 * Removal of a named in-memory database. 303 * 304 * PUBLIC: int __db_inmem_remove __P((DB *, DB_TXN *, const char *)); 305 */ 306int 307__db_inmem_remove(dbp, txn, name) 308 DB *dbp; 309 DB_TXN *txn; 310 const char *name; 311{ 312 DBT fid_dbt, name_dbt; 313 DB_LOCKER *locker; 314 DB_LSN lsn; 315 ENV *env; 316 int ret; 317 318 env = dbp->env; 319 locker = NULL; 320 321 DB_ASSERT(env, name != NULL); 322 323 /* This had better exist if we are trying to do a remove. */ 324 (void)__memp_set_flags(dbp->mpf, DB_MPOOL_NOFILE, 1); 325 if ((ret = __memp_fopen(dbp->mpf, NULL, 326 name, &dbp->dirname, 0, 0, 0)) != 0) 327 return (ret); 328 if ((ret = __memp_get_fileid(dbp->mpf, dbp->fileid)) != 0) 329 return (ret); 330 dbp->preserve_fid = 1; 331 332 if (LOCKING_ON(env)) { 333 if (dbp->locker == NULL && 334 (ret = __lock_id(env, NULL, &dbp->locker)) != 0) 335 return (ret); 336 locker = txn == NULL ? dbp->locker : txn->locker; 337 } 338 339 /* 340 * In a transactional environment, we'll play the same game we play 341 * for databases in the file system -- create a temporary database 342 * and put it in with the current name and then rename this one to 343 * another name. We'll then use a commit-time event to remove the 344 * entry. 345 */ 346 if ((ret = 347 __fop_lock_handle(env, dbp, locker, DB_LOCK_WRITE, NULL, 0)) != 0) 348 return (ret); 349 350 if (!IS_REAL_TXN(txn)) 351 ret = __memp_nameop(env, dbp->fileid, NULL, name, NULL, 1); 352 else if (LOGGING_ON(env)) { 353 if (txn != NULL && (ret = 354 __txn_remevent(env, txn, name, dbp->fileid, 1)) != 0) 355 return (ret); 356 357 DB_INIT_DBT(name_dbt, name, strlen(name) + 1); 358 DB_INIT_DBT(fid_dbt, dbp->fileid, DB_FILE_ID_LEN); 359 ret = __crdel_inmem_remove_log( 360 env, txn, &lsn, 0, &name_dbt, &fid_dbt); 361 } 362 363 return (ret); 364} 365 366/* 367 * __db_subdb_remove -- 368 * Remove a subdatabase. 369 */ 370static int 371__db_subdb_remove(dbp, ip, txn, name, subdb) 372 DB *dbp; 373 DB_THREAD_INFO *ip; 374 DB_TXN *txn; 375 const char *name, *subdb; 376{ 377 DB *mdbp, *sdbp; 378 int ret, t_ret; 379 380 mdbp = sdbp = NULL; 381 382 /* Open the subdatabase. */ 383 if ((ret = __db_create_internal(&sdbp, dbp->env, 0)) != 0) 384 goto err; 385 if (F_ISSET(dbp, DB_AM_NOT_DURABLE) && 386 (ret = __db_set_flags(sdbp, DB_TXN_NOT_DURABLE)) != 0) 387 goto err; 388 if ((ret = __db_open(sdbp, ip, 389 txn, name, subdb, DB_UNKNOWN, DB_WRITEOPEN, 0, PGNO_BASE_MD)) != 0) 390 goto err; 391 392 DB_TEST_RECOVERY(sdbp, DB_TEST_PREDESTROY, ret, name); 393 394 /* Free up the pages in the subdatabase. */ 395 switch (sdbp->type) { 396 case DB_BTREE: 397 case DB_RECNO: 398 if ((ret = __bam_reclaim(sdbp, ip, txn)) != 0) 399 goto err; 400 break; 401 case DB_HASH: 402 if ((ret = __ham_reclaim(sdbp, ip, txn)) != 0) 403 goto err; 404 break; 405 case DB_QUEUE: 406 case DB_UNKNOWN: 407 default: 408 ret = __db_unknown_type( 409 sdbp->env, "__db_subdb_remove", sdbp->type); 410 goto err; 411 } 412 413 /* 414 * Remove the entry from the main database and free the subdatabase 415 * metadata page. 416 */ 417 if ((ret = __db_master_open(sdbp, ip, txn, name, 0, 0, &mdbp)) != 0) 418 goto err; 419 420 if ((ret = __db_master_update(mdbp, 421 sdbp, ip, txn, subdb, sdbp->type, MU_REMOVE, NULL, 0)) != 0) 422 goto err; 423 424 DB_TEST_RECOVERY(sdbp, DB_TEST_POSTDESTROY, ret, name); 425 426DB_TEST_RECOVERY_LABEL 427err: 428 /* Close the main and subdatabases. */ 429 if ((t_ret = __db_close(sdbp, txn, DB_NOSYNC)) != 0 && ret == 0) 430 ret = t_ret; 431 432 if (mdbp != NULL && 433 (t_ret = __db_close(mdbp, txn, DB_NOSYNC)) != 0 && ret == 0) 434 ret = t_ret; 435 436 return (ret); 437} 438 439static int 440__db_dbtxn_remove(dbp, ip, txn, name, subdb) 441 DB *dbp; 442 DB_THREAD_INFO *ip; 443 DB_TXN *txn; 444 const char *name, *subdb; 445{ 446 ENV *env; 447 int ret; 448 char *tmpname; 449 450 env = dbp->env; 451 tmpname = NULL; 452 453 /* 454 * This is a transactional remove, so we have to keep the name 455 * of the file locked until the transaction commits. As a result, 456 * we implement remove by renaming the file to some other name 457 * (which creates a dummy named file as a placeholder for the 458 * file being rename/dremoved) and then deleting that file as 459 * a delayed remove at commit. 460 */ 461 if ((ret = __db_backup_name(env, 462 F_ISSET(dbp, DB_AM_INMEM) ? subdb : name, txn, &tmpname)) != 0) 463 return (ret); 464 465 DB_TEST_RECOVERY(dbp, DB_TEST_PREDESTROY, ret, name); 466 467 if ((ret = __db_rename_int(dbp, 468 txn->thread_info, txn, name, subdb, tmpname)) != 0) 469 goto err; 470 471 /* 472 * The internal removes will also translate into delayed removes. 473 */ 474 if (dbp->db_am_remove != NULL && 475 (ret = dbp->db_am_remove(dbp, ip, txn, tmpname, NULL, 0)) != 0) 476 goto err; 477 478 ret = F_ISSET(dbp, DB_AM_INMEM) ? 479 __db_inmem_remove(dbp, txn, tmpname) : 480 __fop_remove(env, 481 txn, dbp->fileid, tmpname, &dbp->dirname, DB_APP_DATA, 482 F_ISSET(dbp, DB_AM_NOT_DURABLE) ? DB_LOG_NOT_DURABLE : 0); 483 484 DB_TEST_RECOVERY(dbp, DB_TEST_POSTDESTROY, ret, name); 485 486err: 487DB_TEST_RECOVERY_LABEL 488 if (tmpname != NULL) 489 __os_free(env, tmpname); 490 491 return (ret); 492} 493