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