mirror of
https://github.com/postgres/postgres.git
synced 2025-07-28 23:42:10 +03:00
Avoid holding a directory FD open across assorted SRF calls.
This extends the fixes made in commit 085b6b667
to other SRFs with the
same bug, namely pg_logdir_ls(), pgrowlocks(), pg_timezone_names(),
pg_ls_dir(), and pg_tablespace_databases().
Also adjust various comments and documentation to warn against
expecting to clean up resources during a ValuePerCall SRF's final
call.
Back-patch to all supported branches, since these functions were
all born broken.
Justin Pryzby, with cosmetic tweaks by me
Discussion: https://postgr.es/m/20200308173103.GC1357@telsasoft.com
This commit is contained in:
@ -45,11 +45,6 @@ PG_FUNCTION_INFO_V1(pg_file_rename);
|
||||
PG_FUNCTION_INFO_V1(pg_file_unlink);
|
||||
PG_FUNCTION_INFO_V1(pg_logdir_ls);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char *location;
|
||||
DIR *dirdesc;
|
||||
} directory_fctx;
|
||||
|
||||
/*-----------------------
|
||||
* some helper functions
|
||||
@ -281,9 +276,14 @@ pg_file_unlink(PG_FUNCTION_ARGS)
|
||||
Datum
|
||||
pg_logdir_ls(PG_FUNCTION_ARGS)
|
||||
{
|
||||
FuncCallContext *funcctx;
|
||||
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
|
||||
bool randomAccess;
|
||||
TupleDesc tupdesc;
|
||||
Tuplestorestate *tupstore;
|
||||
AttInMetadata *attinmeta;
|
||||
DIR *dirdesc;
|
||||
struct dirent *de;
|
||||
directory_fctx *fctx;
|
||||
MemoryContext oldcontext;
|
||||
|
||||
if (!superuser())
|
||||
ereport(ERROR,
|
||||
@ -293,43 +293,39 @@ pg_logdir_ls(PG_FUNCTION_ARGS)
|
||||
if (strcmp(Log_filename, "postgresql-%Y-%m-%d_%H%M%S.log") != 0)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
(errmsg("the log_filename parameter must equal 'postgresql-%%Y-%%m-%%d_%%H%%M%%S.log'"))));
|
||||
errmsg("the log_filename parameter must equal 'postgresql-%%Y-%%m-%%d_%%H%%M%%S.log'")));
|
||||
|
||||
if (SRF_IS_FIRSTCALL())
|
||||
{
|
||||
MemoryContext oldcontext;
|
||||
TupleDesc tupdesc;
|
||||
/* check to see if caller supports us returning a tuplestore */
|
||||
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("set-valued function called in context that cannot accept a set")));
|
||||
if (!(rsinfo->allowedModes & SFRM_Materialize))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("materialize mode required, but it is not allowed in this context")));
|
||||
|
||||
funcctx = SRF_FIRSTCALL_INIT();
|
||||
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
|
||||
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
|
||||
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
|
||||
|
||||
fctx = palloc(sizeof(directory_fctx));
|
||||
tupdesc = CreateTemplateTupleDesc(2, false);
|
||||
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "starttime",
|
||||
TIMESTAMPOID, -1, 0);
|
||||
TupleDescInitEntry(tupdesc, (AttrNumber) 2, "filename",
|
||||
TEXTOID, -1, 0);
|
||||
|
||||
tupdesc = CreateTemplateTupleDesc(2, false);
|
||||
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "starttime",
|
||||
TIMESTAMPOID, -1, 0);
|
||||
TupleDescInitEntry(tupdesc, (AttrNumber) 2, "filename",
|
||||
TEXTOID, -1, 0);
|
||||
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
|
||||
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
|
||||
rsinfo->returnMode = SFRM_Materialize;
|
||||
rsinfo->setResult = tupstore;
|
||||
rsinfo->setDesc = tupdesc;
|
||||
|
||||
funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
|
||||
MemoryContextSwitchTo(oldcontext);
|
||||
|
||||
fctx->location = pstrdup(Log_directory);
|
||||
fctx->dirdesc = AllocateDir(fctx->location);
|
||||
attinmeta = TupleDescGetAttInMetadata(tupdesc);
|
||||
|
||||
if (!fctx->dirdesc)
|
||||
ereport(ERROR,
|
||||
(errcode_for_file_access(),
|
||||
errmsg("could not read directory \"%s\": %m",
|
||||
fctx->location)));
|
||||
|
||||
funcctx->user_fctx = fctx;
|
||||
MemoryContextSwitchTo(oldcontext);
|
||||
}
|
||||
|
||||
funcctx = SRF_PERCALL_SETUP();
|
||||
fctx = (directory_fctx *) funcctx->user_fctx;
|
||||
|
||||
while ((de = ReadDir(fctx->dirdesc, fctx->location)) != NULL)
|
||||
dirdesc = AllocateDir(Log_directory);
|
||||
while ((de = ReadDir(dirdesc, Log_directory)) != NULL)
|
||||
{
|
||||
char *values[2];
|
||||
HeapTuple tuple;
|
||||
@ -366,13 +362,13 @@ pg_logdir_ls(PG_FUNCTION_ARGS)
|
||||
/* Seems the timestamp is OK; prepare and return tuple */
|
||||
|
||||
values[0] = timestampbuf;
|
||||
values[1] = psprintf("%s/%s", fctx->location, de->d_name);
|
||||
values[1] = psprintf("%s/%s", Log_directory, de->d_name);
|
||||
|
||||
tuple = BuildTupleFromCStrings(funcctx->attinmeta, values);
|
||||
tuple = BuildTupleFromCStrings(attinmeta, values);
|
||||
|
||||
SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));
|
||||
tuplestore_puttuple(tupstore, tuple);
|
||||
}
|
||||
|
||||
FreeDir(fctx->dirdesc);
|
||||
SRF_RETURN_DONE(funcctx);
|
||||
FreeDir(dirdesc);
|
||||
return (Datum) 0;
|
||||
}
|
||||
|
@ -52,13 +52,6 @@ PG_FUNCTION_INFO_V1(pgrowlocks);
|
||||
|
||||
#define NCHARS 32
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Relation rel;
|
||||
HeapScanDesc scan;
|
||||
int ncolumns;
|
||||
} MyData;
|
||||
|
||||
#define Atnum_tid 0
|
||||
#define Atnum_xmax 1
|
||||
#define Atnum_ismulti 2
|
||||
@ -69,77 +62,80 @@ typedef struct
|
||||
Datum
|
||||
pgrowlocks(PG_FUNCTION_ARGS)
|
||||
{
|
||||
FuncCallContext *funcctx;
|
||||
text *relname = PG_GETARG_TEXT_PP(0);
|
||||
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
|
||||
bool randomAccess;
|
||||
TupleDesc tupdesc;
|
||||
Tuplestorestate *tupstore;
|
||||
AttInMetadata *attinmeta;
|
||||
Relation rel;
|
||||
RangeVar *relrv;
|
||||
HeapScanDesc scan;
|
||||
HeapTuple tuple;
|
||||
TupleDesc tupdesc;
|
||||
AttInMetadata *attinmeta;
|
||||
Datum result;
|
||||
MyData *mydata;
|
||||
Relation rel;
|
||||
MemoryContext oldcontext;
|
||||
AclResult aclresult;
|
||||
char **values;
|
||||
|
||||
if (SRF_IS_FIRSTCALL())
|
||||
{
|
||||
text *relname;
|
||||
RangeVar *relrv;
|
||||
MemoryContext oldcontext;
|
||||
AclResult aclresult;
|
||||
/* check to see if caller supports us returning a tuplestore */
|
||||
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("set-valued function called in context that cannot accept a set")));
|
||||
if (!(rsinfo->allowedModes & SFRM_Materialize))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("materialize mode required, but it is not allowed in this context")));
|
||||
|
||||
funcctx = SRF_FIRSTCALL_INIT();
|
||||
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
|
||||
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
|
||||
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
|
||||
|
||||
/* 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");
|
||||
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
|
||||
elog(ERROR, "return type must be a row type");
|
||||
|
||||
attinmeta = TupleDescGetAttInMetadata(tupdesc);
|
||||
funcctx->attinmeta = attinmeta;
|
||||
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
|
||||
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
|
||||
rsinfo->returnMode = SFRM_Materialize;
|
||||
rsinfo->setResult = tupstore;
|
||||
rsinfo->setDesc = tupdesc;
|
||||
|
||||
relname = PG_GETARG_TEXT_PP(0);
|
||||
relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
|
||||
rel = relation_openrv(relrv, AccessShareLock);
|
||||
MemoryContextSwitchTo(oldcontext);
|
||||
|
||||
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("\"%s\" is a partitioned table",
|
||||
RelationGetRelationName(rel)),
|
||||
errdetail("Partitioned tables do not contain rows.")));
|
||||
else if (rel->rd_rel->relkind != RELKIND_RELATION)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("\"%s\" is not a table",
|
||||
RelationGetRelationName(rel))));
|
||||
/* Access the table */
|
||||
relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
|
||||
rel = relation_openrv(relrv, AccessShareLock);
|
||||
|
||||
/*
|
||||
* check permissions: must have SELECT on table or be in
|
||||
* pg_stat_scan_tables
|
||||
*/
|
||||
aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
|
||||
ACL_SELECT);
|
||||
if (aclresult != ACLCHECK_OK)
|
||||
aclresult = is_member_of_role(GetUserId(), DEFAULT_ROLE_STAT_SCAN_TABLES) ? ACLCHECK_OK : ACLCHECK_NO_PRIV;
|
||||
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("\"%s\" is a partitioned table",
|
||||
RelationGetRelationName(rel)),
|
||||
errdetail("Partitioned tables do not contain rows.")));
|
||||
else if (rel->rd_rel->relkind != RELKIND_RELATION)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("\"%s\" is not a table",
|
||||
RelationGetRelationName(rel))));
|
||||
|
||||
if (aclresult != ACLCHECK_OK)
|
||||
aclcheck_error(aclresult, ACL_KIND_CLASS,
|
||||
RelationGetRelationName(rel));
|
||||
/*
|
||||
* check permissions: must have SELECT on table or be in
|
||||
* pg_stat_scan_tables
|
||||
*/
|
||||
aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
|
||||
ACL_SELECT);
|
||||
if (aclresult != ACLCHECK_OK)
|
||||
aclresult = is_member_of_role(GetUserId(), DEFAULT_ROLE_STAT_SCAN_TABLES) ? ACLCHECK_OK : ACLCHECK_NO_PRIV;
|
||||
|
||||
scan = heap_beginscan(rel, GetActiveSnapshot(), 0, NULL);
|
||||
mydata = palloc(sizeof(*mydata));
|
||||
mydata->rel = rel;
|
||||
mydata->scan = scan;
|
||||
mydata->ncolumns = tupdesc->natts;
|
||||
funcctx->user_fctx = mydata;
|
||||
if (aclresult != ACLCHECK_OK)
|
||||
aclcheck_error(aclresult, ACL_KIND_CLASS,
|
||||
RelationGetRelationName(rel));
|
||||
|
||||
MemoryContextSwitchTo(oldcontext);
|
||||
}
|
||||
/* Scan the relation */
|
||||
scan = heap_beginscan(rel, GetActiveSnapshot(), 0, NULL);
|
||||
|
||||
funcctx = SRF_PERCALL_SETUP();
|
||||
attinmeta = funcctx->attinmeta;
|
||||
mydata = (MyData *) funcctx->user_fctx;
|
||||
scan = mydata->scan;
|
||||
attinmeta = TupleDescGetAttInMetadata(tupdesc);
|
||||
|
||||
values = (char **) palloc(tupdesc->natts * sizeof(char *));
|
||||
|
||||
/* scan the relation */
|
||||
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
|
||||
{
|
||||
HTSU_Result htsu;
|
||||
@ -160,10 +156,6 @@ pgrowlocks(PG_FUNCTION_ARGS)
|
||||
*/
|
||||
if (htsu == HeapTupleBeingUpdated)
|
||||
{
|
||||
char **values;
|
||||
|
||||
values = (char **) palloc(mydata->ncolumns * sizeof(char *));
|
||||
|
||||
values[Atnum_tid] = (char *) DirectFunctionCall1(tidout,
|
||||
PointerGetDatum(&tuple->t_self));
|
||||
|
||||
@ -288,16 +280,7 @@ pgrowlocks(PG_FUNCTION_ARGS)
|
||||
|
||||
/* build a tuple */
|
||||
tuple = BuildTupleFromCStrings(attinmeta, values);
|
||||
|
||||
/* make the tuple into a datum */
|
||||
result = HeapTupleGetDatum(tuple);
|
||||
|
||||
/*
|
||||
* no need to pfree what we allocated; it's on a short-lived
|
||||
* memory context anyway
|
||||
*/
|
||||
|
||||
SRF_RETURN_NEXT(funcctx, result);
|
||||
tuplestore_puttuple(tupstore, tuple);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -306,7 +289,6 @@ pgrowlocks(PG_FUNCTION_ARGS)
|
||||
}
|
||||
|
||||
heap_endscan(scan);
|
||||
heap_close(mydata->rel, AccessShareLock);
|
||||
|
||||
SRF_RETURN_DONE(funcctx);
|
||||
heap_close(rel, AccessShareLock);
|
||||
return (Datum) 0;
|
||||
}
|
||||
|
Reference in New Issue
Block a user