mirror of
https://github.com/postgres/postgres.git
synced 2025-06-13 07:41:39 +03:00
This would cause the function to crash when more than one page is considered as broken and reported in the SRF. Reported-by: Noriyoshi Shinoda Discussion: https://postgr.es/m/TU4PR8401MB11523D42C315AAF822E74275EE170@TU4PR8401MB1152.NAMPRD84.PROD.OUTLOOK.COM
230 lines
6.0 KiB
C
230 lines
6.0 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* pagefuncs.c
|
|
* Functions for features related to relation pages.
|
|
*
|
|
* Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* src/backend/utils/adt/pagefuncs.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
#include "access/relation.h"
|
|
#include "funcapi.h"
|
|
#include "miscadmin.h"
|
|
#include "storage/bufmgr.h"
|
|
#include "storage/lmgr.h"
|
|
#include "storage/smgr.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/syscache.h"
|
|
|
|
static void check_one_relation(TupleDesc tupdesc, Tuplestorestate *tupstore,
|
|
Oid relid, ForkNumber single_forknum);
|
|
static void check_relation_fork(TupleDesc tupdesc, Tuplestorestate *tupstore,
|
|
Relation relation, ForkNumber forknum);
|
|
|
|
/*
|
|
* callback arguments for check_pages_error_callback()
|
|
*/
|
|
typedef struct CheckPagesErrorInfo
|
|
{
|
|
char *path;
|
|
BlockNumber blkno;
|
|
} CheckPagesErrorInfo;
|
|
|
|
/*
|
|
* Error callback specific to check_relation_fork().
|
|
*/
|
|
static void
|
|
check_pages_error_callback(void *arg)
|
|
{
|
|
CheckPagesErrorInfo *errinfo = (CheckPagesErrorInfo *) arg;
|
|
|
|
errcontext("while checking page %u of path %s",
|
|
errinfo->blkno, errinfo->path);
|
|
}
|
|
|
|
/*
|
|
* pg_relation_check_pages
|
|
*
|
|
* Check the state of all the pages for one or more fork types in the given
|
|
* relation.
|
|
*/
|
|
Datum
|
|
pg_relation_check_pages(PG_FUNCTION_ARGS)
|
|
{
|
|
Oid relid;
|
|
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
|
|
TupleDesc tupdesc;
|
|
Tuplestorestate *tupstore;
|
|
MemoryContext per_query_ctx;
|
|
MemoryContext oldcontext;
|
|
ForkNumber forknum;
|
|
|
|
/* Switch into long-lived context to construct returned data structures */
|
|
per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
|
|
oldcontext = MemoryContextSwitchTo(per_query_ctx);
|
|
|
|
/* Build a tuple descriptor for our result type */
|
|
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
|
|
elog(ERROR, "return type must be a row type");
|
|
|
|
tupstore = tuplestore_begin_heap(true, false, work_mem);
|
|
rsinfo->returnMode = SFRM_Materialize;
|
|
rsinfo->setResult = tupstore;
|
|
rsinfo->setDesc = tupdesc;
|
|
|
|
MemoryContextSwitchTo(oldcontext);
|
|
|
|
/* handle arguments */
|
|
if (PG_ARGISNULL(0))
|
|
{
|
|
/* Just leave if nothing is defined */
|
|
PG_RETURN_VOID();
|
|
}
|
|
|
|
/* By default all the forks of a relation are checked */
|
|
if (PG_ARGISNULL(1))
|
|
forknum = InvalidForkNumber;
|
|
else
|
|
{
|
|
const char *forkname = TextDatumGetCString(PG_GETARG_TEXT_PP(1));
|
|
|
|
forknum = forkname_to_number(forkname);
|
|
}
|
|
|
|
relid = PG_GETARG_OID(0);
|
|
|
|
check_one_relation(tupdesc, tupstore, relid, forknum);
|
|
tuplestore_donestoring(tupstore);
|
|
|
|
return (Datum) 0;
|
|
}
|
|
|
|
/*
|
|
* Perform the check on a single relation, possibly filtered with a single
|
|
* fork. This function will check if the given relation exists or not, as
|
|
* a relation could be dropped after checking for the list of relations and
|
|
* before getting here, and we don't want to error out in this case.
|
|
*/
|
|
static void
|
|
check_one_relation(TupleDesc tupdesc, Tuplestorestate *tupstore,
|
|
Oid relid, ForkNumber single_forknum)
|
|
{
|
|
Relation relation;
|
|
ForkNumber forknum;
|
|
|
|
/* Check if relation exists. leaving if there is no such relation */
|
|
if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid)))
|
|
return;
|
|
|
|
relation = relation_open(relid, AccessShareLock);
|
|
|
|
/*
|
|
* Sanity checks, returning no results if not supported. Temporary
|
|
* relations and relations without storage are out of scope.
|
|
*/
|
|
if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind) ||
|
|
relation->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
|
|
{
|
|
relation_close(relation, AccessShareLock);
|
|
return;
|
|
}
|
|
|
|
RelationOpenSmgr(relation);
|
|
|
|
for (forknum = 0; forknum <= MAX_FORKNUM; forknum++)
|
|
{
|
|
if (single_forknum != InvalidForkNumber && single_forknum != forknum)
|
|
continue;
|
|
|
|
if (smgrexists(relation->rd_smgr, forknum))
|
|
check_relation_fork(tupdesc, tupstore, relation, forknum);
|
|
}
|
|
|
|
relation_close(relation, AccessShareLock);
|
|
}
|
|
|
|
/*
|
|
* For a given relation and fork, do the real work of iterating over all pages
|
|
* and doing the check. Caller must hold an AccessShareLock lock on the given
|
|
* relation.
|
|
*/
|
|
static void
|
|
check_relation_fork(TupleDesc tupdesc, Tuplestorestate *tupstore,
|
|
Relation relation, ForkNumber forknum)
|
|
{
|
|
BlockNumber blkno,
|
|
nblocks;
|
|
SMgrRelation smgr = relation->rd_smgr;
|
|
char *path;
|
|
CheckPagesErrorInfo errinfo;
|
|
ErrorContextCallback errcallback;
|
|
|
|
/* Number of output arguments in the SRF */
|
|
#define PG_CHECK_RELATION_COLS 2
|
|
|
|
Assert(CheckRelationLockedByMe(relation, AccessShareLock, true));
|
|
|
|
/*
|
|
* We remember the number of blocks here. Since caller must hold a lock
|
|
* on the relation, we know that it won't be truncated while we are
|
|
* iterating over the blocks. Any block added after this function started
|
|
* will not be checked.
|
|
*/
|
|
nblocks = RelationGetNumberOfBlocksInFork(relation, forknum);
|
|
|
|
path = relpathbackend(smgr->smgr_rnode.node,
|
|
smgr->smgr_rnode.backend,
|
|
forknum);
|
|
|
|
/*
|
|
* Error context to print some information about blocks and relations
|
|
* impacted by corruptions.
|
|
*/
|
|
errinfo.path = pstrdup(path);
|
|
errinfo.blkno = 0;
|
|
errcallback.callback = check_pages_error_callback;
|
|
errcallback.arg = (void *) &errinfo;
|
|
errcallback.previous = error_context_stack;
|
|
error_context_stack = &errcallback;
|
|
|
|
for (blkno = 0; blkno < nblocks; blkno++)
|
|
{
|
|
Datum values[PG_CHECK_RELATION_COLS];
|
|
bool nulls[PG_CHECK_RELATION_COLS];
|
|
int i = 0;
|
|
|
|
/* Update block number for the error context */
|
|
errinfo.blkno = blkno;
|
|
|
|
CHECK_FOR_INTERRUPTS();
|
|
|
|
/* Check the given buffer */
|
|
if (CheckBuffer(smgr, forknum, blkno))
|
|
continue;
|
|
|
|
memset(values, 0, sizeof(values));
|
|
memset(nulls, 0, sizeof(nulls));
|
|
|
|
values[i++] = CStringGetTextDatum(path);
|
|
values[i++] = Int64GetDatum((int64) blkno);
|
|
|
|
Assert(i == PG_CHECK_RELATION_COLS);
|
|
|
|
/* Save the corrupted blocks in the tuplestore. */
|
|
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
|
|
}
|
|
|
|
pfree(path);
|
|
|
|
/* Pop the error context stack */
|
|
error_context_stack = errcallback.previous;
|
|
}
|