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:
@ -4893,12 +4893,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;
|
||||
@ -4907,59 +4907,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, false);
|
||||
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(),
|
||||
@ -4978,25 +4960,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;
|
||||
}
|
||||
|
@ -31,13 +31,6 @@
|
||||
#include "utils/memutils.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.
|
||||
@ -393,9 +386,15 @@ 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 (!superuser())
|
||||
@ -403,62 +402,68 @@ pg_ls_dir(PG_FUNCTION_ARGS)
|
||||
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
||||
(errmsg("must be superuser to get directory listings"))));
|
||||
|
||||
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_P(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, false);
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -363,72 +363,82 @@ pg_rotate_logfile(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, false);
|
||||
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)
|
||||
{
|
||||
char *subdir;
|
||||
DIR *dirdesc;
|
||||
Oid datOid = atooid(de->d_name);
|
||||
char *subdir;
|
||||
DIR *dirdesc2;
|
||||
Datum values[1];
|
||||
bool nulls[1];
|
||||
|
||||
/* this test skips . and .., but is awfully weak */
|
||||
if (!datOid)
|
||||
@ -436,24 +446,27 @@ 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);
|
||||
dirdesc = AllocateDir(subdir);
|
||||
while ((de = ReadDir(dirdesc, subdir)) != NULL)
|
||||
subdir = psprintf("%s/%s", location, de->d_name);
|
||||
dirdesc2 = AllocateDir(subdir);
|
||||
while ((de = ReadDir(dirdesc2, subdir)) != NULL)
|
||||
{
|
||||
if (strcmp(de->d_name, ".") != 0 && strcmp(de->d_name, "..") != 0)
|
||||
break;
|
||||
}
|
||||
FreeDir(dirdesc);
|
||||
FreeDir(dirdesc2);
|
||||
pfree(subdir);
|
||||
|
||||
if (!de)
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
@ -388,8 +388,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
|
||||
@ -426,10 +424,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.
|
||||
|
Reference in New Issue
Block a user