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