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
11static const char sccsid[] = "@(#)log_findckp.c	10.17 (Sleepycat) 9/17/98";
12#endif /* not lint */
13
14#ifndef NO_SYSTEM_INCLUDES
15#include <sys/types.h>
16
17#include <errno.h>
18#include <string.h>
19#endif
20
21#include "db_int.h"
22#include "shqueue.h"
23#include "log.h"
24#include "txn.h"
25#include "common_ext.h"
26
27/*
28 * __log_findckp --
29 *
30 * Looks for the most recent checkpoint that occurs before the most recent
31 * checkpoint LSN, subject to the constraint that there must be at least two
32 * checkpoints.  The reason you need two checkpoints is that you might have
33 * crashed during the most recent one and may not have a copy of all the
34 * open files.  This is the point from which recovery can start and the
35 * point up to which archival/truncation can take place.  Checkpoints in
36 * the log look like:
37 *
38 * -------------------------------------------------------------------
39 *  | ckp A, ckplsn 100 |  .... record .... | ckp B, ckplsn 600 | ...
40 * -------------------------------------------------------------------
41 *         LSN 500                                 LSN 1000
42 *
43 * If we read what log returns from using the DB_CKP parameter to logput,
44 * we'll get the record at LSN 1000.  The checkpoint LSN there is 600.
45 * Now we have to scan backwards looking for a checkpoint before LSN 600.
46 * We find one at 500.  This means that we can truncate the log before
47 * 500 or run recovery beginning at 500.
48 *
49 * Returns 0 if we find a suitable checkpoint or we retrieved the
50 * first record in the log from which to start.
51 * Returns DB_NOTFOUND if there are no log records.
52 * Returns errno on error.
53 *
54 * PUBLIC: int __log_findckp __P((DB_LOG *, DB_LSN *));
55 */
56int
57__log_findckp(lp, lsnp)
58	DB_LOG *lp;
59	DB_LSN *lsnp;
60{
61	DBT data;
62	DB_LSN ckp_lsn, final_ckp, last_ckp, next_lsn;
63	__txn_ckp_args *ckp_args;
64	int ret, verbose;
65
66	verbose = lp->dbenv != NULL && lp->dbenv->db_verbose != 0;
67
68	/*
69	 * Need to find the appropriate point from which to begin
70	 * recovery.
71	 */
72	memset(&data, 0, sizeof(data));
73	if (F_ISSET(lp, DB_AM_THREAD))
74		F_SET(&data, DB_DBT_MALLOC);
75	ZERO_LSN(ckp_lsn);
76	if ((ret = log_get(lp, &last_ckp, &data, DB_CHECKPOINT)) != 0)
77		if (ret == ENOENT)
78			goto get_first;
79		else
80			return (ret);
81
82	final_ckp = last_ckp;
83	next_lsn = last_ckp;
84	do {
85		if (F_ISSET(lp, DB_AM_THREAD))
86			__os_free(data.data, data.size);
87
88		if ((ret = log_get(lp, &next_lsn, &data, DB_SET)) != 0)
89			return (ret);
90		if ((ret = __txn_ckp_read(data.data, &ckp_args)) != 0) {
91			if (F_ISSET(lp, DB_AM_THREAD))
92				__os_free(data.data, data.size);
93			return (ret);
94		}
95		if (IS_ZERO_LSN(ckp_lsn))
96			ckp_lsn = ckp_args->ckp_lsn;
97		if (verbose) {
98			__db_err(lp->dbenv, "Checkpoint at: [%lu][%lu]",
99			    (u_long)last_ckp.file, (u_long)last_ckp.offset);
100			__db_err(lp->dbenv, "Checkpoint LSN: [%lu][%lu]",
101			    (u_long)ckp_args->ckp_lsn.file,
102			    (u_long)ckp_args->ckp_lsn.offset);
103			__db_err(lp->dbenv, "Previous checkpoint: [%lu][%lu]",
104			    (u_long)ckp_args->last_ckp.file,
105			    (u_long)ckp_args->last_ckp.offset);
106		}
107		last_ckp = next_lsn;
108		next_lsn = ckp_args->last_ckp;
109		__os_free(ckp_args, sizeof(*ckp_args));
110
111		/*
112		 * Keep looping until either you 1) run out of checkpoints,
113		 * 2) you've found a checkpoint before the most recent
114		 * checkpoint's LSN and you have at least 2 checkpoints.
115		 */
116	} while (!IS_ZERO_LSN(next_lsn) &&
117	    (log_compare(&last_ckp, &ckp_lsn) > 0 ||
118	    log_compare(&final_ckp, &last_ckp) == 0));
119
120	if (F_ISSET(lp, DB_AM_THREAD))
121		__os_free(data.data, data.size);
122
123	/*
124	 * At this point, either, next_lsn is ZERO or ckp_lsn is the
125	 * checkpoint lsn and last_ckp is the LSN of the last checkpoint
126	 * before ckp_lsn.  If the compare in the loop is still true, then
127	 * next_lsn must be 0 and we need to roll forward from the
128	 * beginning of the log.
129	 */
130	if (log_compare(&last_ckp, &ckp_lsn) > 0 ||
131	    log_compare(&final_ckp, &last_ckp) == 0) {
132get_first:	if ((ret = log_get(lp, &last_ckp, &data, DB_FIRST)) != 0)
133			return (ret);
134		if (F_ISSET(lp, DB_AM_THREAD))
135			__os_free(data.data, data.size);
136	}
137	*lsnp = last_ckp;
138
139	return (IS_ZERO_LSN(last_ckp) ? DB_NOTFOUND : 0);
140}
141