1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-07 00:36:50 +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:
Tom Lane
2020-03-16 21:05:29 -04:00
parent f340977a4f
commit 2a89455aad
10 changed files with 387 additions and 347 deletions

View File

@ -4758,12 +4758,12 @@ pg_timezone_abbrevs(PG_FUNCTION_ARGS)
Datum
pg_timezone_names(PG_FUNCTION_ARGS)
{
MemoryContext oldcontext;
FuncCallContext *funcctx;
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
TupleDesc tupdesc;
Tuplestorestate *tupstore;
pg_tzenum *tzenum;
pg_tz *tz;
Datum result;
HeapTuple tuple;
Datum values[4];
bool nulls[4];
int tzoff;
@ -4772,59 +4772,41 @@ pg_timezone_names(PG_FUNCTION_ARGS)
const char *tzn;
Interval *resInterval;
struct pg_tm itm;
MemoryContext oldcontext;
/* stuff done only on the first call of the function */
if (SRF_IS_FIRSTCALL())
{
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")));
/* create a function context for cross-call persistence */
funcctx = SRF_FIRSTCALL_INIT();
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
/*
* switch to memory context appropriate for multiple function calls
*/
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
elog(ERROR, "return type must be a row type");
/* initialize timezone scanning code */
tzenum = pg_tzenumerate_start();
funcctx->user_fctx = (void *) tzenum;
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;
/*
* build tupdesc for result tuples. This must match this function's
* pg_proc entry!
*/
tupdesc = CreateTemplateTupleDesc(4);
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name",
TEXTOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 2, "abbrev",
TEXTOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 3, "utc_offset",
INTERVALOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 4, "is_dst",
BOOLOID, -1, 0);
MemoryContextSwitchTo(oldcontext);
funcctx->tuple_desc = BlessTupleDesc(tupdesc);
MemoryContextSwitchTo(oldcontext);
}
/* stuff done on every call of the function */
funcctx = SRF_PERCALL_SETUP();
tzenum = (pg_tzenum *) funcctx->user_fctx;
/* initialize timezone scanning code */
tzenum = pg_tzenumerate_start();
/* search for another zone to display */
for (;;)
{
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
tz = pg_tzenumerate_next(tzenum);
MemoryContextSwitchTo(oldcontext);
if (!tz)
{
pg_tzenumerate_end(tzenum);
funcctx->user_fctx = NULL;
SRF_RETURN_DONE(funcctx);
}
break;
/* Convert now() to local time in this zone */
if (timestamp2tm(GetCurrentTransactionStartTimestamp(),
@ -4843,25 +4825,22 @@ pg_timezone_names(PG_FUNCTION_ARGS)
if (tzn && strlen(tzn) > 31)
continue;
/* Found a displayable zone */
break;
MemSet(nulls, 0, sizeof(nulls));
values[0] = CStringGetTextDatum(pg_get_timezone_name(tz));
values[1] = CStringGetTextDatum(tzn ? tzn : "");
MemSet(&itm, 0, sizeof(struct pg_tm));
itm.tm_sec = -tzoff;
resInterval = (Interval *) palloc(sizeof(Interval));
tm2interval(&itm, 0, resInterval);
values[2] = IntervalPGetDatum(resInterval);
values[3] = BoolGetDatum(tm.tm_isdst > 0);
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
}
MemSet(nulls, 0, sizeof(nulls));
values[0] = CStringGetTextDatum(pg_get_timezone_name(tz));
values[1] = CStringGetTextDatum(tzn ? tzn : "");
MemSet(&itm, 0, sizeof(struct pg_tm));
itm.tm_sec = -tzoff;
resInterval = (Interval *) palloc(sizeof(Interval));
tm2interval(&itm, 0, resInterval);
values[2] = IntervalPGetDatum(resInterval);
values[3] = BoolGetDatum(tm.tm_isdst > 0);
tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
result = HeapTupleGetDatum(tuple);
SRF_RETURN_NEXT(funcctx, result);
pg_tzenumerate_end(tzenum);
return (Datum) 0;
}

View File

@ -35,13 +35,6 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
typedef struct
{
char *location;
DIR *dirdesc;
bool include_dot_dirs;
} directory_fctx;
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@ -446,67 +439,79 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
FuncCallContext *funcctx;
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
char *location;
bool missing_ok = false;
bool include_dot_dirs = false;
bool randomAccess;
TupleDesc tupdesc;
Tuplestorestate *tupstore;
DIR *dirdesc;
struct dirent *de;
directory_fctx *fctx;
MemoryContext oldcontext;
if (SRF_IS_FIRSTCALL())
location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
/* check the optional arguments */
if (PG_NARGS() == 3)
{
bool missing_ok = false;
bool include_dot_dirs = false;
/* check the optional arguments */
if (PG_NARGS() == 3)
{
if (!PG_ARGISNULL(1))
missing_ok = PG_GETARG_BOOL(1);
if (!PG_ARGISNULL(2))
include_dot_dirs = PG_GETARG_BOOL(2);
}
funcctx = SRF_FIRSTCALL_INIT();
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
fctx = palloc(sizeof(directory_fctx));
fctx->location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
fctx->include_dot_dirs = include_dot_dirs;
fctx->dirdesc = AllocateDir(fctx->location);
if (!fctx->dirdesc)
{
if (missing_ok && errno == ENOENT)
{
MemoryContextSwitchTo(oldcontext);
SRF_RETURN_DONE(funcctx);
}
else
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not open directory \"%s\": %m",
fctx->location)));
}
funcctx->user_fctx = fctx;
MemoryContextSwitchTo(oldcontext);
if (!PG_ARGISNULL(1))
missing_ok = PG_GETARG_BOOL(1);
if (!PG_ARGISNULL(2))
include_dot_dirs = PG_GETARG_BOOL(2);
}
funcctx = SRF_PERCALL_SETUP();
fctx = (directory_fctx *) funcctx->user_fctx;
/* 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")));
while ((de = ReadDir(fctx->dirdesc, fctx->location)) != NULL)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
tupdesc = CreateTemplateTupleDesc(1);
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", 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;
MemoryContextSwitchTo(oldcontext);
dirdesc = AllocateDir(location);
if (!dirdesc)
{
if (!fctx->include_dot_dirs &&
/* Return empty tuplestore if appropriate */
if (missing_ok && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, location)) != NULL)
{
Datum values[1];
bool nulls[1];
if (!include_dot_dirs &&
(strcmp(de->d_name, ".") == 0 ||
strcmp(de->d_name, "..") == 0))
continue;
SRF_RETURN_NEXT(funcctx, CStringGetTextDatum(de->d_name));
values[0] = CStringGetTextDatum(de->d_name);
nulls[0] = false;
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
}
FreeDir(fctx->dirdesc);
SRF_RETURN_DONE(funcctx);
FreeDir(dirdesc);
return (Datum) 0;
}
/*
@ -547,8 +552,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!(rsinfo->allowedModes & SFRM_Materialize))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("materialize mode required, but it is not "
"allowed in this context")));
errmsg("materialize mode required, but it is not allowed in this context")));
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
@ -574,10 +578,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
{
/* Return empty tuplestore if appropriate */
if (missing_ok && errno == ENOENT)
{
tuplestore_donestoring(tupstore);
return (Datum) 0;
}
/* Otherwise, we can let ReadDir() throw the error */
}
@ -612,7 +613,6 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
}
FreeDir(dirdesc);
tuplestore_donestoring(tupstore);
return (Datum) 0;
}

View File

@ -195,72 +195,82 @@ current_query(PG_FUNCTION_ARGS)
/* Function to find out which databases make use of a tablespace */
typedef struct
{
char *location;
DIR *dirdesc;
} ts_db_fctx;
Datum
pg_tablespace_databases(PG_FUNCTION_ARGS)
{
FuncCallContext *funcctx;
Oid tablespaceOid = PG_GETARG_OID(0);
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
TupleDesc tupdesc;
Tuplestorestate *tupstore;
char *location;
DIR *dirdesc;
struct dirent *de;
ts_db_fctx *fctx;
MemoryContext oldcontext;
if (SRF_IS_FIRSTCALL())
/* 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")));
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
tupdesc = CreateTemplateTupleDesc(1);
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_tablespace_databases",
OIDOID, -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;
MemoryContextSwitchTo(oldcontext);
if (tablespaceOid == GLOBALTABLESPACE_OID)
{
MemoryContext oldcontext;
Oid tablespaceOid = PG_GETARG_OID(0);
funcctx = SRF_FIRSTCALL_INIT();
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
fctx = palloc(sizeof(ts_db_fctx));
if (tablespaceOid == GLOBALTABLESPACE_OID)
{
fctx->dirdesc = NULL;
ereport(WARNING,
(errmsg("global tablespace never has databases")));
}
else
{
if (tablespaceOid == DEFAULTTABLESPACE_OID)
fctx->location = psprintf("base");
else
fctx->location = psprintf("pg_tblspc/%u/%s", tablespaceOid,
TABLESPACE_VERSION_DIRECTORY);
fctx->dirdesc = AllocateDir(fctx->location);
if (!fctx->dirdesc)
{
/* the only expected error is ENOENT */
if (errno != ENOENT)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not open directory \"%s\": %m",
fctx->location)));
ereport(WARNING,
(errmsg("%u is not a tablespace OID", tablespaceOid)));
}
}
funcctx->user_fctx = fctx;
MemoryContextSwitchTo(oldcontext);
ereport(WARNING,
(errmsg("global tablespace never has databases")));
/* return empty tuplestore */
return (Datum) 0;
}
funcctx = SRF_PERCALL_SETUP();
fctx = (ts_db_fctx *) funcctx->user_fctx;
if (tablespaceOid == DEFAULTTABLESPACE_OID)
location = psprintf("base");
else
location = psprintf("pg_tblspc/%u/%s", tablespaceOid,
TABLESPACE_VERSION_DIRECTORY);
if (!fctx->dirdesc) /* not a tablespace */
SRF_RETURN_DONE(funcctx);
dirdesc = AllocateDir(location);
while ((de = ReadDir(fctx->dirdesc, fctx->location)) != NULL)
if (!dirdesc)
{
/* the only expected error is ENOENT */
if (errno != ENOENT)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not open directory \"%s\": %m",
location)));
ereport(WARNING,
(errmsg("%u is not a tablespace OID", tablespaceOid)));
/* return empty tuplestore */
return (Datum) 0;
}
while ((de = ReadDir(dirdesc, location)) != NULL)
{
Oid datOid = atooid(de->d_name);
char *subdir;
bool isempty;
Datum values[1];
bool nulls[1];
/* this test skips . and .., but is awfully weak */
if (!datOid)
@ -268,18 +278,21 @@ pg_tablespace_databases(PG_FUNCTION_ARGS)
/* if database subdir is empty, don't report tablespace as used */
subdir = psprintf("%s/%s", fctx->location, de->d_name);
subdir = psprintf("%s/%s", location, de->d_name);
isempty = directory_is_empty(subdir);
pfree(subdir);
if (isempty)
continue; /* indeed, nothing in it */
SRF_RETURN_NEXT(funcctx, ObjectIdGetDatum(datOid));
values[0] = ObjectIdGetDatum(datOid);
nulls[0] = false;
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
}
FreeDir(fctx->dirdesc);
SRF_RETURN_DONE(funcctx);
FreeDir(dirdesc);
return (Datum) 0;
}

View File

@ -239,8 +239,6 @@ tuple toaster will decide whether toasting is needed.
Functions Accepting or Returning Sets
-------------------------------------
[ this section revised 29-Aug-2002 for 7.3 ]
If a function is marked in pg_proc as returning a set, then it is called
with fcinfo->resultinfo pointing to a node of type ReturnSetInfo. A
function that desires to return a set should raise an error "called in
@ -277,10 +275,16 @@ been returned, the next call should set isDone to ExprEndResult and return a
null result. (Note it is possible to return an empty set by doing this on
the first call.)
The ReturnSetInfo node also contains a link to the ExprContext within which
the function is being evaluated. This is useful for value-per-call functions
that need to close down internal state when they are not run to completion:
they can register a shutdown callback function in the ExprContext.
Value-per-call functions MUST NOT assume that they will be run to completion;
the executor might simply stop calling them, for example because of a LIMIT.
Therefore, it's unsafe to attempt to perform any resource cleanup in the
final call. It's usually not necessary to clean up memory, anyway. If it's
necessary to clean up other types of resources, such as file descriptors,
one can register a shutdown callback function in the ExprContext pointed to
by the ReturnSetInfo node. (But note that file descriptors are a limited
resource, so it's generally unwise to hold those open across calls; SRFs
that need file access are better written to do it in a single call using
Materialize mode.)
Materialize mode works like this: the function creates a Tuplestore holding
the (possibly empty) result set, and returns it. There are no multiple calls.