1/*-
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 1999,2008 Oracle.  All rights reserved.
5 *
6 * $Id: qam_verify.c,v 12.18 2008/03/13 15:44:50 mbrey Exp $
7 */
8
9#include "db_config.h"
10
11#include "db_int.h"
12#include "dbinc/db_page.h"
13#include "dbinc/db_verify.h"
14#include "dbinc/db_am.h"
15#include "dbinc/mp.h"
16#include "dbinc/qam.h"
17/*
18 * __qam_vrfy_meta --
19 *	Verify the queue-specific part of a metadata page.
20 *
21 * PUBLIC: int __qam_vrfy_meta __P((DB *, VRFY_DBINFO *, QMETA *,
22 * PUBLIC:     db_pgno_t, u_int32_t));
23 */
24int
25__qam_vrfy_meta(dbp, vdp, meta, pgno, flags)
26	DB *dbp;
27	VRFY_DBINFO *vdp;
28	QMETA *meta;
29	db_pgno_t pgno;
30	u_int32_t flags;
31{
32	ENV *env;
33	QUEUE *qp;
34	VRFY_PAGEINFO *pip;
35	db_pgno_t *extents, extid, first, last;
36	size_t len;
37	int count, i, isbad, nextents, ret, t_ret;
38	char *buf, **names;
39
40	COMPQUIET(count, 0);
41
42	env = dbp->env;
43	qp = (QUEUE *)dbp->q_internal;
44	extents = NULL;
45	first = last = 0;
46	isbad = 0;
47	buf = NULL;
48	names = NULL;
49
50	if ((ret = __db_vrfy_getpageinfo(vdp, pgno, &pip)) != 0)
51		return (ret);
52
53	/*
54	 * Queue can't be used in subdatabases, so if this isn't set
55	 * something very odd is going on.
56	 */
57	if (!F_ISSET(pip, VRFY_INCOMPLETE))
58		EPRINT((env, "Page %lu: queue databases must be one-per-file",
59		    (u_long)pgno));
60
61	/*
62	 * Because the metapage pointers are rolled forward by
63	 * aborting transactions, the extent of the queue may
64	 * extend beyond the allocated pages, so we do
65	 * not check that meta_current is within the allocated
66	 * pages.
67	 */
68
69	/*
70	 * re_len:  If this is bad, we can't safely verify queue data pages, so
71	 * return DB_VERIFY_FATAL
72	 */
73	if (DB_ALIGN(meta->re_len + sizeof(QAMDATA) - 1, sizeof(u_int32_t)) *
74	    meta->rec_page + QPAGE_SZ(dbp) > dbp->pgsize) {
75		EPRINT((env,
76   "Page %lu: queue record length %lu too high for page size and recs/page",
77		    (u_long)pgno, (u_long)meta->re_len));
78		ret = DB_VERIFY_FATAL;
79		goto err;
80	} else {
81		/*
82		 * We initialize the Queue internal pointer;  we may need
83		 * it when handling extents.  It would get set up in open,
84		 * if we called open normally, but we don't.
85		 */
86		vdp->re_pad = meta->re_pad;
87		qp->re_pad = (int)meta->re_pad;
88		qp->re_len = vdp->re_len = meta->re_len;
89		qp->rec_page = vdp->rec_page = meta->rec_page;
90		qp->page_ext = vdp->page_ext = meta->page_ext;
91	}
92
93	/*
94	 * There's no formal maximum extentsize, and a 0 value represents
95	 * no extents, so there's nothing to verify.
96	 *
97	 * Note that since QUEUE databases can't have subdatabases, it's an
98	 * error to see more than one QUEUE metadata page in a single
99	 * verifier run.  Theoretically, this should really be a structure
100	 * rather than a per-page check, but since we're setting qp fields
101	 * here (and have only one qp to set) we raise the alarm now if
102	 * this assumption fails.  (We need the qp info to be reasonable
103	 * before we do per-page verification of queue extents.)
104	 */
105	if (F_ISSET(vdp, VRFY_QMETA_SET)) {
106		isbad = 1;
107		EPRINT((env,
108		    "Page %lu: database contains multiple Queue metadata pages",
109		    (u_long)pgno));
110		goto err;
111	}
112	F_SET(vdp, VRFY_QMETA_SET);
113	qp->page_ext = meta->page_ext;
114	dbp->pgsize = meta->dbmeta.pagesize;
115	qp->q_meta = pgno;
116	qp->q_root = pgno + 1;
117	vdp->first_recno = meta->first_recno;
118	vdp->last_recno = meta->cur_recno;
119	if (qp->page_ext != 0) {
120		first = QAM_RECNO_EXTENT(dbp, vdp->first_recno);
121		last = QAM_RECNO_EXTENT(dbp, vdp->last_recno);
122	}
123
124	/*
125	 * Look in the data directory to see if there are any extents
126	 * around that are not in the range of the queue.  If so,
127	 * then report that and look there if we are salvaging.
128	 */
129
130	if ((ret = __db_appname(env,
131	    DB_APP_DATA, qp->dir, 0, NULL, &buf)) != 0)
132		goto err;
133	if ((ret = __os_dirlist(env, buf, 0, &names, &count)) != 0)
134		goto err;
135	__os_free(env, buf);
136	buf = NULL;
137
138	len = strlen(QUEUE_EXTENT_HEAD) + strlen(qp->name) + 1;
139	if ((ret = __os_malloc(env, len, &buf)) != 0)
140		goto err;
141	len = (size_t)snprintf(buf, len, QUEUE_EXTENT_HEAD, qp->name);
142	for (i = nextents = 0; i < count; i++) {
143		if (strncmp(names[i], buf, len) == 0) {
144			/* Only save extents out of bounds. */
145			extid = (db_pgno_t)strtoul(&names[i][len], NULL, 10);
146			if (qp->page_ext != 0 &&
147			    (last > first ?
148			    (extid >= first && extid <= last) :
149			    (extid >= first || extid <= last)))
150				continue;
151			if (extents == NULL && (ret = __os_malloc(
152			     env, (size_t)(count - i) * sizeof(extid),
153			     &extents)) != 0)
154				goto err;
155			extents[nextents] = extid;
156			nextents++;
157		}
158	}
159	if (nextents > 0)
160		__db_errx(env,
161		     "Warning: %d extra extent files found", nextents);
162	vdp->nextents = nextents;
163	vdp->extents = extents;
164
165err:	if ((t_ret = __db_vrfy_putpageinfo(env, vdp, pip)) != 0 && ret == 0)
166		ret = t_ret;
167	if (names != NULL)
168		__os_dirfree(env, names, count);
169	if (buf != NULL)
170		__os_free(env, buf);
171	if (ret != 0 && extents != NULL)
172		__os_free(env, extents);
173	if (LF_ISSET(DB_SALVAGE) &&
174	   (t_ret = __db_salvage_markdone(vdp, pgno)) != 0 && ret == 0)
175		ret = t_ret;
176	return (ret == 0 && isbad == 1 ? DB_VERIFY_BAD : ret);
177}
178
179/*
180 * __qam_vrfy_data --
181 *	Verify a queue data page.
182 *
183 * PUBLIC: int __qam_vrfy_data __P((DB *, VRFY_DBINFO *, QPAGE *,
184 * PUBLIC:     db_pgno_t, u_int32_t));
185 */
186int
187__qam_vrfy_data(dbp, vdp, h, pgno, flags)
188	DB *dbp;
189	VRFY_DBINFO *vdp;
190	QPAGE *h;
191	db_pgno_t pgno;
192	u_int32_t flags;
193{
194	DB fakedb;
195	struct __queue fakeq;
196	QAMDATA *qp;
197	db_recno_t i;
198
199	/*
200	 * Not much to do here, except make sure that flags are reasonable.
201	 *
202	 * QAM_GET_RECORD assumes a properly initialized q_internal
203	 * structure, however, and we don't have one, so we play
204	 * some gross games to fake it out.
205	 */
206	fakedb.q_internal = &fakeq;
207	fakedb.flags = dbp->flags;
208	fakeq.re_len = vdp->re_len;
209
210	for (i = 0; i < vdp->rec_page; i++) {
211		qp = QAM_GET_RECORD(&fakedb, h, i);
212		if ((u_int8_t *)qp >= (u_int8_t *)h + dbp->pgsize) {
213			EPRINT((dbp->env,
214		    "Page %lu: queue record %lu extends past end of page",
215			    (u_long)pgno, (u_long)i));
216			return (DB_VERIFY_BAD);
217		}
218
219		if (qp->flags & ~(QAM_VALID | QAM_SET)) {
220			EPRINT((dbp->env,
221			    "Page %lu: queue record %lu has bad flags (%#lx)",
222			    (u_long)pgno, (u_long)i, (u_long)qp->flags));
223			return (DB_VERIFY_BAD);
224		}
225	}
226
227	return (0);
228}
229
230/*
231 * __qam_vrfy_structure --
232 *	Verify a queue database structure, such as it is.
233 *
234 * PUBLIC: int __qam_vrfy_structure __P((DB *, VRFY_DBINFO *, u_int32_t));
235 */
236int
237__qam_vrfy_structure(dbp, vdp, flags)
238	DB *dbp;
239	VRFY_DBINFO *vdp;
240	u_int32_t flags;
241{
242	VRFY_PAGEINFO *pip;
243	db_pgno_t i;
244	int ret, isbad;
245
246	isbad = 0;
247
248	if ((ret = __db_vrfy_getpageinfo(vdp, PGNO_BASE_MD, &pip)) != 0)
249		return (ret);
250
251	if (pip->type != P_QAMMETA) {
252		EPRINT((dbp->env,
253		    "Page %lu: queue database has no meta page",
254		    (u_long)PGNO_BASE_MD));
255		isbad = 1;
256		goto err;
257	}
258
259	if ((ret = __db_vrfy_pgset_inc(vdp->pgset, vdp->thread_info, 0)) != 0)
260		goto err;
261
262	for (i = 1; i <= vdp->last_pgno; i++) {
263		/* Send feedback to the application about our progress. */
264		if (!LF_ISSET(DB_SALVAGE))
265			__db_vrfy_struct_feedback(dbp, vdp);
266
267		if ((ret = __db_vrfy_putpageinfo(dbp->env, vdp, pip)) != 0 ||
268		    (ret = __db_vrfy_getpageinfo(vdp, i, &pip)) != 0)
269			return (ret);
270		if (!F_ISSET(pip, VRFY_IS_ALLZEROES) &&
271		    pip->type != P_QAMDATA) {
272			EPRINT((dbp->env,
273		    "Page %lu: queue database page of incorrect type %lu",
274			    (u_long)i, (u_long)pip->type));
275			isbad = 1;
276			goto err;
277		} else if ((ret = __db_vrfy_pgset_inc(vdp->pgset,
278		    vdp->thread_info, i)) != 0)
279			goto err;
280	}
281
282err:	if ((ret = __db_vrfy_putpageinfo(dbp->env, vdp, pip)) != 0)
283		return (ret);
284	return (isbad == 1 ? DB_VERIFY_BAD : 0);
285}
286
287/*
288 * __qam_vrfy_walkqueue --
289 *    Do a "walkpages" per-page verification pass over the set of Queue
290 * extent pages.
291 *
292 * PUBLIC: int __qam_vrfy_walkqueue __P((DB *, VRFY_DBINFO *, void *,
293 * PUBLIC:    int (*)(void *, const void *), u_int32_t));
294 */
295int
296__qam_vrfy_walkqueue(dbp, vdp, handle, callback, flags)
297	DB *dbp;
298	VRFY_DBINFO *vdp;
299	void *handle;
300	int (*callback) __P((void *, const void *));
301	u_int32_t flags;
302{
303	DBC *dbc;
304	ENV *env;
305	PAGE *h;
306	QUEUE *qp;
307	VRFY_PAGEINFO *pip;
308	db_pgno_t first, i, last, pg_ext, stop;
309	int isbad, nextents, ret, t_ret;
310
311	COMPQUIET(h, NULL);
312
313	env = dbp->env;
314	qp = dbp->q_internal;
315	pip = NULL;
316	pg_ext = qp->page_ext;
317	isbad = ret = t_ret = 0;
318	h = NULL;
319
320	/* If this database has no extents, we've seen all the pages already. */
321	if (pg_ext == 0)
322		return (0);
323
324	first = QAM_RECNO_PAGE(dbp, vdp->first_recno);
325	last = QAM_RECNO_PAGE(dbp, vdp->last_recno);
326
327	i = first;
328	if (first > last)
329		stop = QAM_RECNO_PAGE(dbp, UINT32_MAX);
330	else
331		stop = last;
332	nextents = vdp->nextents;
333
334	/* Verify/salvage each page. */
335	if ((ret = __db_cursor(dbp, vdp->thread_info, NULL, &dbc, 0)) != 0)
336		return (ret);
337begin:	for (; i <= stop; i++) {
338		/*
339		 * If DB_SALVAGE is set, we inspect our database of completed
340		 * pages, and skip any we've already printed in the subdb pass.
341		 */
342		if (LF_ISSET(DB_SALVAGE) && (__db_salvage_isdone(vdp, i) != 0))
343			continue;
344		if ((t_ret = __qam_fget(dbc, &i, 0, &h)) != 0) {
345			if (t_ret == ENOENT || t_ret == DB_PAGE_NOTFOUND) {
346				i += (pg_ext - ((i - 1) % pg_ext)) - 1;
347				continue;
348			}
349
350			/*
351			 * If an individual page get fails, keep going iff
352			 * we're salvaging.
353			 */
354			if (LF_ISSET(DB_SALVAGE)) {
355				if (ret == 0)
356					ret = t_ret;
357				continue;
358			}
359			h = NULL;
360			ret = t_ret;
361			goto err;
362		}
363
364		if (LF_ISSET(DB_SALVAGE)) {
365			/*
366			 * We pretty much don't want to quit unless a
367			 * bomb hits.  May as well return that something
368			 * was screwy, however.
369			 */
370			if ((t_ret = __db_salvage(dbp,
371			    vdp, i, h, handle, callback, flags)) != 0) {
372				if (ret == 0)
373					ret = t_ret;
374				isbad = 1;
375			}
376		} else {
377			/*
378			 * If we are not salvaging, and we get any error
379			 * other than DB_VERIFY_BAD, return immediately;
380			 * it may not be safe to proceed.  If we get
381			 * DB_VERIFY_BAD, keep going;  listing more errors
382			 * may make it easier to diagnose problems and
383			 * determine the magnitude of the corruption.
384			 */
385			if ((ret = __db_vrfy_common(dbp,
386			    vdp, h, i, flags)) == DB_VERIFY_BAD)
387				isbad = 1;
388			else if (ret != 0)
389				goto err;
390
391			__db_vrfy_struct_feedback(dbp, vdp);
392
393			if ((ret = __db_vrfy_getpageinfo(vdp, i, &pip)) != 0)
394				goto err;
395			if (F_ISSET(pip, VRFY_IS_ALLZEROES))
396				goto put;
397			if (pip->type != P_QAMDATA) {
398				EPRINT((env,
399		    "Page %lu: queue database page of incorrect type %lu",
400				    (u_long)i, (u_long)pip->type));
401				isbad = 1;
402				goto err;
403			}
404			if ((ret = __db_vrfy_pgset_inc(vdp->pgset,
405			    vdp->thread_info, i)) != 0)
406				goto err;
407			if ((ret = __qam_vrfy_data(dbp, vdp,
408			    (QPAGE *)h, i, flags)) == DB_VERIFY_BAD)
409				isbad = 1;
410			else if (ret != 0)
411				goto err;
412
413put:			if ((ret = __db_vrfy_putpageinfo(env, vdp, pip)) != 0)
414				goto err1;
415			pip = NULL;
416		}
417
418		/* Again, keep going iff we're salvaging. */
419		if ((t_ret = __qam_fput(dbc, i, h, dbp->priority)) != 0) {
420			if (LF_ISSET(DB_SALVAGE)) {
421				if (ret == 0)
422					ret = t_ret;
423				continue;
424			}
425			ret = t_ret;
426			goto err1;
427		}
428	}
429
430	if (first > last) {
431		i = 1;
432		stop = last;
433		first = last;
434		goto begin;
435	}
436
437	/*
438	 * Now check to see if there were any lingering
439	 * extents and dump their data.
440	 */
441	if (LF_ISSET(DB_SALVAGE) && nextents != 0) {
442		nextents--;
443		i = 1 +
444		    vdp->extents[nextents] * vdp->page_ext;
445		stop = i + vdp->page_ext;
446		goto begin;
447	}
448
449	if (0) {
450err:		if (h != NULL &&
451		     (t_ret = __qam_fput(dbc, i, h, dbp->priority)) != 0
452		     && ret == 0)
453			ret = t_ret;
454		if (pip != NULL &&
455		     (t_ret = __db_vrfy_putpageinfo(env, vdp, pip)) != 0
456		     && ret == 0)
457			ret = t_ret;
458	}
459err1:	if (dbc != NULL && (t_ret = __dbc_close(dbc)) != 0 && ret == 0)
460		ret = t_ret;
461	return ((isbad == 1 && ret == 0) ? DB_VERIFY_BAD : ret);
462}
463
464/*
465 * __qam_salvage --
466 *	Safely dump out all recnos and data on a queue page.
467 *
468 * PUBLIC: int __qam_salvage __P((DB *, VRFY_DBINFO *, db_pgno_t, PAGE *,
469 * PUBLIC:     void *, int (*)(void *, const void *), u_int32_t));
470 */
471int
472__qam_salvage(dbp, vdp, pgno, h, handle, callback, flags)
473	DB *dbp;
474	VRFY_DBINFO *vdp;
475	db_pgno_t pgno;
476	PAGE *h;
477	void *handle;
478	int (*callback) __P((void *, const void *));
479	u_int32_t flags;
480{
481	DBT dbt, key;
482	QAMDATA *qp, *qep;
483	db_recno_t recno;
484	int ret, err_ret, t_ret;
485	u_int32_t pagesize, qlen;
486	u_int32_t i;
487
488	memset(&dbt, 0, sizeof(DBT));
489	memset(&key, 0, sizeof(DBT));
490
491	err_ret = ret = 0;
492
493	pagesize = (u_int32_t)dbp->mpf->mfp->stat.st_pagesize;
494	qlen = ((QUEUE *)dbp->q_internal)->re_len;
495	dbt.size = qlen;
496	key.data = &recno;
497	key.size = sizeof(recno);
498	recno = (pgno - 1) * QAM_RECNO_PER_PAGE(dbp) + 1;
499	i = 0;
500	qep = (QAMDATA *)((u_int8_t *)h + pagesize - qlen);
501	for (qp = QAM_GET_RECORD(dbp, h, i); qp < qep;
502	    recno++, i++, qp = QAM_GET_RECORD(dbp, h, i)) {
503		if (F_ISSET(qp, ~(QAM_VALID|QAM_SET)))
504			continue;
505		if (!F_ISSET(qp, QAM_SET))
506			continue;
507
508		if (!LF_ISSET(DB_AGGRESSIVE) && !F_ISSET(qp, QAM_VALID))
509			continue;
510
511		dbt.data = qp->data;
512		if ((ret = __db_vrfy_prdbt(&key,
513		    0, " ", handle, callback, 1, vdp)) != 0)
514			err_ret = ret;
515
516		if ((ret = __db_vrfy_prdbt(&dbt,
517		    0, " ", handle, callback, 0, vdp)) != 0)
518			err_ret = ret;
519	}
520
521	if ((t_ret = __db_salvage_markdone(vdp, pgno)) != 0)
522		return (t_ret);
523	return ((ret == 0 && err_ret != 0) ? err_ret : ret);
524}
525