1 /*- 2 * See the file LICENSE for redistribution information. 3 * 4 * Copyright (c) 1996, 1997, 1998 5 * Sleepycat Software. All rights reserved. 6 */ 7 8 #include "config.h" 9 10 #ifndef lint 11 static const char copyright[] = 12 "@(#) Copyright (c) 1996, 1997, 1998\n\ 13 Sleepycat Software Inc. All rights reserved.\n"; 14 static const char sccsid[] = "@(#)db_apprec.c 10.33 (Sleepycat) 10/5/98"; 15 #endif 16 17 #ifndef NO_SYSTEM_INCLUDES 18 #include <sys/types.h> 19 20 #include <errno.h> 21 #include <string.h> 22 #include <time.h> 23 #endif 24 25 #include "db_int.h" 26 #include "shqueue.h" 27 #include "db_page.h" 28 #include "db_dispatch.h" 29 #include "db_am.h" 30 #include "log.h" 31 #include "txn.h" 32 #include "common_ext.h" 33 34 /* 35 * __db_apprec -- 36 * Perform recovery. 37 * 38 * PUBLIC: int __db_apprec __P((DB_ENV *, u_int32_t)); 39 */ 40 int 41 __db_apprec(dbenv, flags) 42 DB_ENV *dbenv; 43 u_int32_t flags; 44 { 45 DBT data; 46 DB_LOG *lp; 47 DB_LSN ckp_lsn, first_lsn, lsn, open_lsn; 48 __txn_ckp_args *ckp_args; 49 time_t now; 50 u_int32_t is_thread; 51 int ret; 52 void *txninfo; 53 54 lp = dbenv->lg_info; 55 56 /* Initialize the transaction list. */ 57 if ((ret = __db_txnlist_init(&txninfo)) != 0) 58 return (ret); 59 60 /* 61 * Save the state of the thread flag -- we don't need it on at the 62 * moment because we're single-threaded until recovery is complete. 63 */ 64 is_thread = F_ISSET(lp, DB_AM_THREAD); 65 F_CLR(lp, DB_AM_THREAD); 66 67 /* 68 * Recovery is done in three passes: 69 * Pass #0: 70 * We need to find the position from which we will open files 71 * We need to open files beginning with the last to next 72 * checkpoint because we might have crashed after writing the 73 * last checkpoint record, but before having written out all 74 * the open file information. 75 * Pass #1: 76 * Read forward through the log from the second to last checkpoint 77 * opening and closing files so that at the end of the log we have 78 * the "current" set of files open. 79 * Pass #2: 80 * Read backward through the log undoing any uncompleted TXNs. 81 * If doing catastrophic recovery, we read to the beginning of 82 * the log, otherwise, to the most recent checkpoint that occurs 83 * before the most recent checkpoint LSN, which is returned by 84 * __log_findckp(). During this pass, checkpoint file information 85 * is ignored, and file openings and closings are undone. 86 * Pass #3: 87 * Read forward through the log from the LSN found in pass #2, 88 * redoing any committed TXNs. During this pass, checkpoint 89 * file information is ignored, and file openings and closings 90 * are redone. 91 */ 92 93 /* 94 * Find the second to last checkpoint in the log. This is the point 95 * from which we want to begin pass #1 (the TXN_OPENFILES pass). 96 */ 97 memset(&data, 0, sizeof(data)); 98 ckp_args = NULL; 99 100 if ((ret = log_get(lp, &ckp_lsn, &data, DB_CHECKPOINT)) != 0) { 101 /* 102 * If we don't find a checkpoint, start from the beginning. 103 * If that fails, we're done. Note, we do not require that 104 * there be log records if we're performing recovery. 105 */ 106 first: if ((ret = log_get(lp, &ckp_lsn, &data, DB_FIRST)) != 0) { 107 if (ret == DB_NOTFOUND) 108 ret = 0; 109 else 110 __db_err(dbenv, "First log record not found"); 111 goto out; 112 } 113 open_lsn = ckp_lsn; 114 } else if ((ret = __txn_ckp_read(data.data, &ckp_args)) != 0) { 115 __db_err(dbenv, "Invalid checkpoint record at [%ld][%ld]\n", 116 (u_long)ckp_lsn.file, (u_long)ckp_lsn.offset); 117 goto out; 118 } else if (IS_ZERO_LSN(ckp_args->last_ckp) || 119 (ret = log_get(lp, &ckp_args->last_ckp, &data, DB_SET)) != 0) 120 goto first; 121 else 122 open_lsn = ckp_args->last_ckp; 123 124 /* 125 * Now, ckp_lsn is either the lsn of the last checkpoint or the lsn 126 * of the first record in the log. Open_lsn is the second to last 127 * checkpoint or the beinning of the log; begin the TXN_OPENFILES 128 * pass from that lsn, and proceed to the end of the log. 129 */ 130 lsn = open_lsn; 131 for (;;) { 132 if (dbenv->tx_recover != NULL) 133 ret = dbenv->tx_recover(lp, 134 &data, &lsn, TXN_OPENFILES, txninfo); 135 else 136 ret = __db_dispatch(lp, 137 &data, &lsn, TXN_OPENFILES, txninfo); 138 if (ret != 0 && ret != DB_TXN_CKP) 139 goto msgerr; 140 if ((ret = log_get(lp, &lsn, &data, DB_NEXT)) != 0) { 141 if (ret == DB_NOTFOUND) 142 break; 143 goto out; 144 } 145 } 146 147 /* 148 * Pass #2. 149 * 150 * Before we can begin pass #2, backward roll phase, we determine how 151 * far back in the log to recover. If we are doing catastrophic 152 * recovery, then we go as far back as we have files. If we are 153 * doing normal recovery, we go as back to the most recent checkpoint 154 * that occurs before the most recent checkpoint LSN. 155 */ 156 if (LF_ISSET(DB_RECOVER_FATAL)) { 157 ZERO_LSN(first_lsn); 158 } else 159 if ((ret = __log_findckp(lp, &first_lsn)) == DB_NOTFOUND) { 160 /* 161 * We don't require that log files exist if recovery 162 * was specified. 163 */ 164 ret = 0; 165 goto out; 166 } 167 168 if (dbenv->db_verbose) 169 __db_err(lp->dbenv, "Recovery starting from [%lu][%lu]", 170 (u_long)first_lsn.file, (u_long)first_lsn.offset); 171 172 for (ret = log_get(lp, &lsn, &data, DB_LAST); 173 ret == 0 && log_compare(&lsn, &first_lsn) > 0; 174 ret = log_get(lp, &lsn, &data, DB_PREV)) { 175 if (dbenv->tx_recover != NULL) 176 ret = dbenv->tx_recover(lp, 177 &data, &lsn, TXN_BACKWARD_ROLL, txninfo); 178 else 179 ret = __db_dispatch(lp, 180 &data, &lsn, TXN_BACKWARD_ROLL, txninfo); 181 if (ret != 0) 182 if (ret != DB_TXN_CKP) 183 goto msgerr; 184 else 185 ret = 0; 186 } 187 if (ret != 0 && ret != DB_NOTFOUND) 188 goto out; 189 190 /* 191 * Pass #3. 192 */ 193 for (ret = log_get(lp, &lsn, &data, DB_NEXT); 194 ret == 0; ret = log_get(lp, &lsn, &data, DB_NEXT)) { 195 if (dbenv->tx_recover != NULL) 196 ret = dbenv->tx_recover(lp, 197 &data, &lsn, TXN_FORWARD_ROLL, txninfo); 198 else 199 ret = __db_dispatch(lp, 200 &data, &lsn, TXN_FORWARD_ROLL, txninfo); 201 if (ret != 0) 202 if (ret != DB_TXN_CKP) 203 goto msgerr; 204 else 205 ret = 0; 206 } 207 if (ret != DB_NOTFOUND) 208 goto out; 209 210 /* Now close all the db files that are open. */ 211 __log_close_files(lp); 212 213 /* 214 * Now set the last checkpoint lsn and the current time, 215 * take a checkpoint, and reset the txnid. 216 */ 217 (void)time(&now); 218 dbenv->tx_info->region->last_ckp = ckp_lsn; 219 dbenv->tx_info->region->time_ckp = (u_int32_t)now; 220 if ((ret = txn_checkpoint(dbenv->tx_info, 0, 0)) != 0) 221 goto out; 222 dbenv->tx_info->region->last_txnid = TXN_MINIMUM; 223 224 if (dbenv->db_verbose) { 225 __db_err(lp->dbenv, "Recovery complete at %.24s", ctime(&now)); 226 __db_err(lp->dbenv, "%s %lx %s [%lu][%lu]", 227 "Maximum transaction id", 228 ((DB_TXNHEAD *)txninfo)->maxid, 229 "Recovery checkpoint", 230 (u_long)dbenv->tx_info->region->last_ckp.file, 231 (u_long)dbenv->tx_info->region->last_ckp.offset); 232 } 233 234 if (0) { 235 msgerr: __db_err(dbenv, "Recovery function for LSN %lu %lu failed", 236 (u_long)lsn.file, (u_long)lsn.offset); 237 } 238 239 out: F_SET(lp, is_thread); 240 __db_txnlist_end(txninfo); 241 if (ckp_args != NULL) 242 __os_free(ckp_args, sizeof(*ckp_args)); 243 244 return (ret); 245 } 246