-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On 08/24/2015 08:52 AM, Joe Conway wrote:
> On 08/24/2015 06:50 AM, Tom Lane wrote:
>> Andrew Dunstan <and...@dunslane.net> writes:
>>> On 08/23/2015 08:58 PM, Michael Paquier wrote:
>>>> I think that's a good thing to have, now I have concerns
>>>> about making this data readable for non-superusers. Cloud
>>>> deployments of Postgres are logically going to block the
>>>> access of this view.
> 
>>> I don't think it exposes any information of great security 
>>> value.
> 
>> We just had that kerfuffle about whether WAL compression posed a 
>> security risk; doesn't that imply that at least the data
>> relevant to WAL position has to be unreadable by non-superusers?
> 
> So pg_config might be fully unrestricted, but pg_controldata might 
> need certain rows filtered based on superuser status? Do you think 
> those rows should be present but redacted, or completely filtered
> out?

Here is the next installment on this. It addresses most of the
previous comments, but does not yet address the issue raised here by Tom
.

Changes:
1.) pg_controldata() function and pg_controldata view added

2.) cleanup_path() moved to port/path.c

3.) extra PG_FUNCTION_INFO_V1() noise removed

Issues needing comment:
a.) Which items need hiding from non-superusers and should the value be
    redacted or the entire result set row be suppressed?

b.) There is a difference in the formatting of timestamps between the
    pg_controldata binary and the builtin function. To see it do:

  diff -c <(pg_controldata) \
  <(psql -qAt -c "select rpad(name || ':', 38) || setting
                  from pg_controldata")

    What I see is:

pg_controldata
! pg_control last modified:             Tue 25 Aug 2015 08:10:42 PM PDT
pg_controldata()
! pg_control last modified:             Tue Aug 25 20:10:42 2015

    Does it matter?

c.) There is some common code between pg_controldata.c in bin and
    pg_controldata.c in backend/utils/misc. Specifically the string
    definitions in printf statements match those in ControlData[], and
    dbState() and wal_level_str() are in both places. Any opinions
    on consolidating them in src/common somewhere?

d.) Still no docs yet - will get to it eventually.

Thanks,

Joe

- -- 
Crunchy Data - http://crunchydata.com
PostgreSQL Support for Secure Enterprises
Consulting, Training, & Open Source Development
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2.0.22 (GNU/Linux)

iQIcBAEBAgAGBQJV3TNnAAoJEDfy90M199hl0OsQAIyTr0hqxwjrGenDpnS4qE8u
UJWVeCpqehFIobxcJ0TICTaQX835fzPGJIiTUwI1Dmz9sgjipvSG1wBmD4bRT93X
WO4e/+Yr5onZ9vNVXlUswPK2kuzehImhmzMSnJ6KDXKkfw2MOZmz56wBb3yIB3lq
K44FDukZ01w9RCGM8H5B/MPNMHIqfBB4wdlKARJZhqeUyPvTJ71iMiqeE77v3AIH
JLmW6kRw8c3NVu/Wa+GVz4FGjIZKR5oazlFYfDTeHXrxV8NIDUFNrKikAW1ScdVK
qSPVjFxoUlbX4W2dd1L1ciGeq83DktYbdKtpZZScQGXwhuq7Y1fHZQwzlxlraB/c
UiqNdxmi7IeUdOIncsKPDmjs7C5yeNj1CRnWHTAQRW98RM42A3TvT2Qlkxm0CVLQ
lZjFVOOMIf4pXYQv6PfiicO6QWYTUSXCa891s/10H2xkS/sMK1yHz3DWSZxVdDdI
dbh5gie/GFro1nwWd8gjkn5KCe917GDBAUBn+QE5TgUPnRhserq6FQBSyVXfZtOQ
o6nRM8vuv9Y06CRoeIgagtDWxippl0OAw442wHyme/PBQZ2842PW8GNNqw+B1HWz
Ir0V5FiZdLLQipwiKT152+8OsOa/NU6wxGFuJr8It/4471h3jU5dxuHO+wQqMDEb
xCn6ebwZaa9oSjHFrfy3
=oMOO
-----END PGP SIGNATURE-----
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index ccc030f..7b5db32 100644
*** a/src/backend/catalog/system_views.sql
--- b/src/backend/catalog/system_views.sql
*************** CREATE VIEW pg_timezone_abbrevs AS
*** 425,430 ****
--- 425,436 ----
  CREATE VIEW pg_timezone_names AS
      SELECT * FROM pg_timezone_names();
  
+ CREATE VIEW pg_config AS
+     SELECT * FROM pg_config();
+ 
+ CREATE VIEW pg_controldata AS
+     SELECT * FROM pg_controldata();
+ 
  -- Statistics views
  
  CREATE VIEW pg_stat_all_tables AS
diff --git a/src/backend/utils/misc/Makefile b/src/backend/utils/misc/Makefile
index 7889101..13b60f7 100644
*** a/src/backend/utils/misc/Makefile
--- b/src/backend/utils/misc/Makefile
*************** include $(top_builddir)/src/Makefile.glo
*** 14,21 ****
  
  override CPPFLAGS := -I. -I$(srcdir) $(CPPFLAGS)
  
! OBJS = guc.o help_config.o pg_rusage.o ps_status.o rls.o \
!        sampling.o superuser.o timeout.o tzparser.o
  
  # This location might depend on the installation directories. Therefore
  # we can't subsitute it into pg_config.h.
--- 14,34 ----
  
  override CPPFLAGS := -I. -I$(srcdir) $(CPPFLAGS)
  
! # don't include subdirectory-path-dependent -I and -L switches
! STD_CPPFLAGS := $(filter-out -I$(top_srcdir)/src/include -I$(top_builddir)/src/include,$(CPPFLAGS))
! STD_LDFLAGS := $(filter-out -L$(top_builddir)/src/port,$(LDFLAGS))
! override CPPFLAGS += -DVAL_CONFIGURE="\"$(configure_args)\""
! override CPPFLAGS += -DVAL_CC="\"$(CC)\""
! override CPPFLAGS += -DVAL_CPPFLAGS="\"$(STD_CPPFLAGS)\""
! override CPPFLAGS += -DVAL_CFLAGS="\"$(CFLAGS)\""
! override CPPFLAGS += -DVAL_CFLAGS_SL="\"$(CFLAGS_SL)\""
! override CPPFLAGS += -DVAL_LDFLAGS="\"$(STD_LDFLAGS)\""
! override CPPFLAGS += -DVAL_LDFLAGS_EX="\"$(LDFLAGS_EX)\""
! override CPPFLAGS += -DVAL_LDFLAGS_SL="\"$(LDFLAGS_SL)\""
! override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
! 
! OBJS = guc.o help_config.o pg_config.o pg_controldata.o pg_rusage.o \
!        ps_status.o rls.o sampling.o superuser.o timeout.o tzparser.o
  
  # This location might depend on the installation directories. Therefore
  # we can't subsitute it into pg_config.h.
diff --git a/src/backend/utils/misc/pg_config.c b/src/backend/utils/misc/pg_config.c
index ...475a9c5 .
*** a/src/backend/utils/misc/pg_config.c
--- b/src/backend/utils/misc/pg_config.c
***************
*** 0 ****
--- 1,280 ----
+ /*-------------------------------------------------------------------------
+  *
+  * pg_config.c
+  *		Expose same output as pg_config except as an SRF
+  *
+  * Copyright (c) 2015, PostgreSQL Global Development Group
+  * ALL RIGHTS RESERVED;
+  *
+  * Permission to use, copy, modify, and distribute this software and its
+  * documentation for any purpose, without fee, and without a written agreement
+  * is hereby granted, provided that the above copyright notice and this
+  * paragraph and the following two paragraphs appear in all copies.
+  *
+  * IN NO EVENT SHALL THE AUTHOR OR DISTRIBUTORS BE LIABLE TO ANY PARTY FOR
+  * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
+  * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
+  * DOCUMENTATION, EVEN IF THE AUTHOR OR DISTRIBUTORS HAVE BEEN ADVISED OF THE
+  * POSSIBILITY OF SUCH DAMAGE.
+  *
+  * THE AUTHOR AND DISTRIBUTORS SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+  * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+  * ON AN "AS IS" BASIS, AND THE AUTHOR AND DISTRIBUTORS HAS NO OBLIGATIONS TO
+  * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+  *
+  */
+ 
+ #include "postgres.h"
+ 
+ #include "funcapi.h"
+ #include "miscadmin.h"
+ #include "catalog/pg_type.h"
+ #include "utils/builtins.h"
+ #include "utils/elog.h"
+ #include "port.h"
+ 
+ static void get_configdata(void);
+ 
+ #ifdef PGDLLIMPORT
+ /* Postgres global */
+ extern PGDLLIMPORT char my_exec_path[];
+ #else
+ /* Postgres global */
+ extern DLLIMPORT char my_exec_path[];
+ #endif /* PGDLLIMPORT */
+ 
+ struct configdata
+ {
+ 	char	   *name;
+ 	char	   *setting;
+ };
+ 
+ static struct configdata ConfigData[] =
+ {
+ 	{"BINDIR", NULL},
+ 	{"DOCDIR", NULL},
+ 	{"HTMLDIR", NULL},
+ 	{"INCLUDEDIR", NULL},
+ 	{"PKGINCLUDEDIR", NULL},
+ 	{"INCLUDEDIR-SERVER", NULL},
+ 	{"LIBDIR", NULL},
+ 	{"PKGLIBDIR", NULL},
+ 	{"LOCALEDIR", NULL},
+ 	{"MANDIR", NULL},
+ 	{"SHAREDIR", NULL},
+ 	{"SYSCONFDIR", NULL},
+ 	{"PGXS", NULL},
+ 	{"CONFIGURE", NULL},
+ 	{"CC", NULL},
+ 	{"CPPFLAGS", NULL},
+ 	{"CFLAGS", NULL},
+ 	{"CFLAGS_SL", NULL},
+ 	{"LDFLAGS", NULL},
+ 	{"LDFLAGS_EX", NULL},
+ 	{"LDFLAGS_SL", NULL},
+ 	{"LIBS", NULL},
+ 	{"VERSION", NULL},
+ 	{NULL, NULL}
+ };
+ 
+ static void get_configdata(void);
+ 
+ Datum
+ pg_config(PG_FUNCTION_ARGS)
+ {
+ 	ReturnSetInfo	   *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ 	Tuplestorestate	   *tupstore;
+ 	HeapTuple			tuple;
+ 	TupleDesc			tupdesc;
+ 	AttInMetadata	   *attinmeta;
+ 	MemoryContext		per_query_ctx;
+ 	MemoryContext		oldcontext;
+ 	char			   *values[2];
+ 	int					i = 0;
+ 
+ 	/* check to see if caller supports us returning a tuplestore */
+ 	if (!rsinfo || !(rsinfo->allowedModes & SFRM_Materialize))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_SYNTAX_ERROR),
+ 				 errmsg("materialize mode required, but it is not "
+ 						"allowed in this context")));
+ 
+ 	per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+ 	oldcontext = MemoryContextSwitchTo(per_query_ctx);
+ 
+ 	/* get the requested return tuple description */
+ 	tupdesc = CreateTupleDescCopy(rsinfo->expectedDesc);
+ 
+ 	/*
+ 	 * Check to make sure we have a reasonable tuple descriptor
+ 	 */
+ 	if (tupdesc->natts != 2 ||
+ 		tupdesc->attrs[0]->atttypid != TEXTOID ||
+ 		tupdesc->attrs[1]->atttypid != TEXTOID)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_SYNTAX_ERROR),
+ 				 errmsg("query-specified return tuple and "
+ 						"function return type are not compatible")));
+ 
+ 	/* OK to use it */
+ 	attinmeta = TupleDescGetAttInMetadata(tupdesc);
+ 
+ 	/* let the caller know we're sending back a tuplestore */
+ 	rsinfo->returnMode = SFRM_Materialize;
+ 
+ 	/* initialize our tuplestore */
+ 	tupstore = tuplestore_begin_heap(true, false, work_mem);
+ 
+ 	get_configdata();
+ 	while (ConfigData[i].name)
+ 	{
+ 		values[0] = ConfigData[i].name;
+ 		values[1] = ConfigData[i].setting;
+ 
+ 		tuple = BuildTupleFromCStrings(attinmeta, values);
+ 		tuplestore_puttuple(tupstore, tuple);
+ 		++i;
+ 	}
+ 	
+ 	/*
+ 	 * no longer need the tuple descriptor reference created by
+ 	 * TupleDescGetAttInMetadata()
+ 	 */
+ 	ReleaseTupleDesc(tupdesc);
+ 
+ 	tuplestore_donestoring(tupstore);
+ 	rsinfo->setResult = tupstore;
+ 
+ 	/*
+ 	 * SFRM_Materialize mode expects us to return a NULL Datum. The actual
+ 	 * tuples are in our tuplestore and passed back through
+ 	 * rsinfo->setResult. rsinfo->setDesc is set to the tuple description
+ 	 * that we actually used to build our tuples with, so the caller can
+ 	 * verify we did what it was expecting.
+ 	 */
+ 	rsinfo->setDesc = tupdesc;
+ 	MemoryContextSwitchTo(oldcontext);
+ 
+ 	return (Datum) 0;
+ }
+ 
+ 
+ static void
+ get_configdata(void)
+ {
+ 	char			path[MAXPGPATH];
+ 	char		   *lastsep;
+ 
+ 	strcpy(path, my_exec_path);
+ 	lastsep = strrchr(path, '/');
+ 	if (lastsep)
+ 		*lastsep = '\0';
+ 	cleanup_path(path);
+ 	ConfigData[0].setting = pstrdup(path);
+ 
+ 	get_doc_path(my_exec_path, path);
+ 	cleanup_path(path);
+ 	ConfigData[1].setting = pstrdup(path);
+ 
+ 	get_html_path(my_exec_path, path);
+ 	cleanup_path(path);
+ 	ConfigData[2].setting = pstrdup(path);
+ 
+ 	get_include_path(my_exec_path, path);
+ 	cleanup_path(path);
+ 	ConfigData[3].setting = pstrdup(path);
+ 
+ 	get_pkginclude_path(my_exec_path, path);
+ 	cleanup_path(path);
+ 	ConfigData[4].setting = pstrdup(path);
+ 
+ 	get_includeserver_path(my_exec_path, path);
+ 	cleanup_path(path);
+ 	ConfigData[5].setting = pstrdup(path);
+ 
+ 	get_lib_path(my_exec_path, path);
+ 	cleanup_path(path);
+ 	ConfigData[6].setting = pstrdup(path);
+ 
+ 	get_pkglib_path(my_exec_path, path);
+ 	cleanup_path(path);
+ 	ConfigData[7].setting = pstrdup(path);
+ 
+ 	get_locale_path(my_exec_path, path);
+ 	cleanup_path(path);
+ 	ConfigData[8].setting = pstrdup(path);
+ 
+ 	get_man_path(my_exec_path, path);
+ 	cleanup_path(path);
+ 	ConfigData[9].setting = pstrdup(path);
+ 
+ 	get_share_path(my_exec_path, path);
+ 	cleanup_path(path);
+ 	ConfigData[10].setting = pstrdup(path);
+ 
+ 	get_etc_path(my_exec_path, path);
+ 	cleanup_path(path);
+ 	ConfigData[11].setting = pstrdup(path);
+ 
+ 	get_pkglib_path(my_exec_path, path);
+ 	strlcat(path, "/pgxs/src/makefiles/pgxs.mk", sizeof(path));
+ 	cleanup_path(path);
+ 	ConfigData[12].setting = pstrdup(path);
+ 
+ #ifdef VAL_CONFIGURE
+ 	ConfigData[13].setting = pstrdup(VAL_CONFIGURE);
+ #else
+ 	ConfigData[13].setting = pstrdup(_("not recorded"));
+ #endif
+ 
+ #ifdef VAL_CC
+ 	ConfigData[14].setting = pstrdup(VAL_CC);
+ #else
+ 	ConfigData[14].setting = pstrdup(_("not recorded"));
+ #endif
+ 
+ #ifdef VAL_CPPFLAGS
+ 	ConfigData[15].setting = pstrdup(VAL_CPPFLAGS);
+ #else
+ 	ConfigData[15].setting = pstrdup(_("not recorded"));
+ #endif
+ 
+ #ifdef VAL_CFLAGS
+ 	ConfigData[16].setting = pstrdup(VAL_CFLAGS);
+ #else
+ 	ConfigData[16].setting = pstrdup(_("not recorded"));
+ #endif
+ 
+ #ifdef VAL_CFLAGS_SL
+ 	ConfigData[17].setting = pstrdup(VAL_CFLAGS_SL);
+ #else
+ 	ConfigData[17].setting = pstrdup(_("not recorded"));
+ #endif
+ 
+ #ifdef VAL_LDFLAGS
+ 	ConfigData[18].setting = pstrdup(VAL_LDFLAGS);
+ #else
+ 	ConfigData[18].setting = pstrdup(_("not recorded"));
+ #endif
+ 
+ #ifdef VAL_LDFLAGS_EX
+ 	ConfigData[19].setting = pstrdup(VAL_LDFLAGS_EX);
+ #else
+ 	ConfigData[19].setting = pstrdup(_("not recorded"));
+ #endif
+ 
+ #ifdef VAL_LDFLAGS_SL
+ 	ConfigData[20].setting = pstrdup(VAL_LDFLAGS_SL);
+ #else
+ 	ConfigData[20].setting = pstrdup(_("not recorded"));
+ #endif
+ 
+ #ifdef VAL_LIBS
+ 	ConfigData[21].setting = pstrdup(VAL_LIBS);
+ #else
+ 	ConfigData[21].setting = pstrdup(_("not recorded"));
+ #endif
+ 
+ 	ConfigData[22].setting = pstrdup("PostgreSQL " PG_VERSION);
+ }
diff --git a/src/backend/utils/misc/pg_controldata.c b/src/backend/utils/misc/pg_controldata.c
index ...e8aa367 .
*** a/src/backend/utils/misc/pg_controldata.c
--- b/src/backend/utils/misc/pg_controldata.c
***************
*** 0 ****
--- 1,515 ----
+ /*-------------------------------------------------------------------------
+  *
+  * pg_controldata.c
+  *		Expose same output as pg_controldata except as an SRF
+  *
+  * Copyright (c) 2015, PostgreSQL Global Development Group
+  * ALL RIGHTS RESERVED;
+  *
+  * Permission to use, copy, modify, and distribute this software and its
+  * documentation for any purpose, without fee, and without a written agreement
+  * is hereby granted, provided that the above copyright notice and this
+  * paragraph and the following two paragraphs appear in all copies.
+  *
+  * IN NO EVENT SHALL THE AUTHOR OR DISTRIBUTORS BE LIABLE TO ANY PARTY FOR
+  * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
+  * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
+  * DOCUMENTATION, EVEN IF THE AUTHOR OR DISTRIBUTORS HAVE BEEN ADVISED OF THE
+  * POSSIBILITY OF SUCH DAMAGE.
+  *
+  * THE AUTHOR AND DISTRIBUTORS SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+  * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+  * ON AN "AS IS" BASIS, AND THE AUTHOR AND DISTRIBUTORS HAS NO OBLIGATIONS TO
+  * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+  *
+  */
+ 
+ #include "postgres.h"
+ 
+ #include <unistd.h>
+ #include <time.h>
+ #include <sys/stat.h>
+ #include <fcntl.h>
+ 
+ #include "funcapi.h"
+ #include "miscadmin.h"
+ #include "access/xlog.h"
+ #include "access/xlog_internal.h"
+ #include "catalog/pg_control.h"
+ #include "catalog/pg_type.h"
+ #include "port/pg_crc32c.h"
+ #include "utils/builtins.h"
+ 
+ struct controldata
+ {
+ 	char	   *name;
+ 	char	   *setting;
+ };
+ 
+ static struct controldata ControlData[] =
+ {
+ 	{gettext_noop("pg_control version number"), NULL},
+ 	{gettext_noop("Catalog version number"), NULL},
+ 	{gettext_noop("Database system identifier"), NULL},
+ 	{gettext_noop("Database cluster state"), NULL},
+ 	{gettext_noop("pg_control last modified"), NULL},
+ 	{gettext_noop("Latest checkpoint location"), NULL},
+ 	{gettext_noop("Prior checkpoint location"), NULL},
+ 	{gettext_noop("Latest checkpoint's REDO location"), NULL},
+ 	{gettext_noop("Latest checkpoint's REDO WAL file"), NULL},
+ 	{gettext_noop("Latest checkpoint's TimeLineID"), NULL},
+ 	{gettext_noop("Latest checkpoint's PrevTimeLineID"), NULL},
+ 	{gettext_noop("Latest checkpoint's full_page_writes"), NULL},
+ 	{gettext_noop("Latest checkpoint's NextXID"), NULL},
+ 	{gettext_noop("Latest checkpoint's NextOID"), NULL},
+ 	{gettext_noop("Latest checkpoint's NextMultiXactId"), NULL},
+ 	{gettext_noop("Latest checkpoint's NextMultiOffset"), NULL},
+ 	{gettext_noop("Latest checkpoint's oldestXID"), NULL},
+ 	{gettext_noop("Latest checkpoint's oldestXID's DB"), NULL},
+ 	{gettext_noop("Latest checkpoint's oldestActiveXID"), NULL},
+ 	{gettext_noop("Latest checkpoint's oldestMultiXid"), NULL},
+ 	{gettext_noop("Latest checkpoint's oldestMulti's DB"), NULL},
+ 	{gettext_noop("Latest checkpoint's oldestCommitTs"), NULL},
+ 	{gettext_noop("Time of latest checkpoint"), NULL},
+ 	{gettext_noop("Fake LSN counter for unlogged rels"), NULL},
+ 	{gettext_noop("Minimum recovery ending location"), NULL},
+ 	{gettext_noop("Min recovery ending loc's timeline"), NULL},
+ 	{gettext_noop("Backup start location"), NULL},
+ 	{gettext_noop("Backup end location"), NULL},
+ 	{gettext_noop("End-of-backup record required"), NULL},
+ 	{gettext_noop("wal_level setting"), NULL},
+ 	{gettext_noop("wal_log_hints setting"), NULL},
+ 	{gettext_noop("max_connections setting"), NULL},
+ 	{gettext_noop("max_worker_processes setting"), NULL},
+ 	{gettext_noop("max_prepared_xacts setting"), NULL},
+ 	{gettext_noop("max_locks_per_xact setting"), NULL},
+ 	{gettext_noop("track_commit_timestamp setting"), NULL},
+ 	{gettext_noop("Maximum data alignment"), NULL},
+ 	{gettext_noop("Database block size"), NULL},
+ 	{gettext_noop("Blocks per segment of large relation"), NULL},
+ 	{gettext_noop("WAL block size"), NULL},
+ 	{gettext_noop("Bytes per WAL segment"), NULL},
+ 	{gettext_noop("Maximum length of identifiers"), NULL},
+ 	{gettext_noop("Maximum columns in an index"), NULL},
+ 	{gettext_noop("Maximum size of a TOAST chunk"), NULL},
+ 	{gettext_noop("Size of a large-object chunk"), NULL},
+ 	{gettext_noop("Date/time type storage"), NULL},
+ 	{gettext_noop("Float4 argument passing"), NULL},
+ 	{gettext_noop("Float8 argument passing"), NULL},
+ 	{gettext_noop("Data page checksum version"), NULL},
+ 	{NULL, NULL}
+ };
+ 
+ 
+ static const char *dbState(DBState state);
+ static void get_controldata(void);
+ 
+ Datum
+ pg_controldata(PG_FUNCTION_ARGS)
+ {
+ 	ReturnSetInfo	   *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ 	Tuplestorestate	   *tupstore;
+ 	HeapTuple			tuple;
+ 	TupleDesc			tupdesc;
+ 	AttInMetadata	   *attinmeta;
+ 	MemoryContext		per_query_ctx;
+ 	MemoryContext		oldcontext;
+ 	char			   *values[2];
+ 	int					i = 0;
+ 
+ 	/* check to see if caller supports us returning a tuplestore */
+ 	if (!rsinfo || !(rsinfo->allowedModes & SFRM_Materialize))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_SYNTAX_ERROR),
+ 				 errmsg("materialize mode required, but it is not "
+ 						"allowed in this context")));
+ 
+ 	per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+ 	oldcontext = MemoryContextSwitchTo(per_query_ctx);
+ 
+ 	/* get the requested return tuple description */
+ 	tupdesc = CreateTupleDescCopy(rsinfo->expectedDesc);
+ 
+ 	/*
+ 	 * Check to make sure we have a reasonable tuple descriptor
+ 	 */
+ 	if (tupdesc->natts != 2 ||
+ 		tupdesc->attrs[0]->atttypid != TEXTOID ||
+ 		tupdesc->attrs[1]->atttypid != TEXTOID)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_SYNTAX_ERROR),
+ 				 errmsg("query-specified return tuple and "
+ 						"function return type are not compatible")));
+ 
+ 	/* OK to use it */
+ 	attinmeta = TupleDescGetAttInMetadata(tupdesc);
+ 
+ 	/* let the caller know we're sending back a tuplestore */
+ 	rsinfo->returnMode = SFRM_Materialize;
+ 
+ 	/* initialize our tuplestore */
+ 	tupstore = tuplestore_begin_heap(true, false, work_mem);
+ 
+ 	get_controldata();
+ 	while (ControlData[i].name)
+ 	{
+ 		values[0] = _(ControlData[i].name);
+ 		values[1] = ControlData[i].setting;
+ 
+ 		tuple = BuildTupleFromCStrings(attinmeta, values);
+ 		tuplestore_puttuple(tupstore, tuple);
+ 		++i;
+ 	}
+ 	
+ 	/*
+ 	 * no longer need the tuple descriptor reference created by
+ 	 * TupleDescGetAttInMetadata()
+ 	 */
+ 	ReleaseTupleDesc(tupdesc);
+ 
+ 	tuplestore_donestoring(tupstore);
+ 	rsinfo->setResult = tupstore;
+ 
+ 	/*
+ 	 * SFRM_Materialize mode expects us to return a NULL Datum. The actual
+ 	 * tuples are in our tuplestore and passed back through
+ 	 * rsinfo->setResult. rsinfo->setDesc is set to the tuple description
+ 	 * that we actually used to build our tuples with, so the caller can
+ 	 * verify we did what it was expecting.
+ 	 */
+ 	rsinfo->setDesc = tupdesc;
+ 	MemoryContextSwitchTo(oldcontext);
+ 
+ 	return (Datum) 0;
+ }
+ 
+ static const char *
+ dbState(DBState state)
+ {
+ 	switch (state)
+ 	{
+ 		case DB_STARTUP:
+ 			return _("starting up");
+ 		case DB_SHUTDOWNED:
+ 			return _("shut down");
+ 		case DB_SHUTDOWNED_IN_RECOVERY:
+ 			return _("shut down in recovery");
+ 		case DB_SHUTDOWNING:
+ 			return _("shutting down");
+ 		case DB_IN_CRASH_RECOVERY:
+ 			return _("in crash recovery");
+ 		case DB_IN_ARCHIVE_RECOVERY:
+ 			return _("in archive recovery");
+ 		case DB_IN_PRODUCTION:
+ 			return _("in production");
+ 	}
+ 	return _("unrecognized status code");
+ }
+ 
+ static const char *
+ wal_level_str(WalLevel wal_level)
+ {
+ 	switch (wal_level)
+ 	{
+ 		case WAL_LEVEL_MINIMAL:
+ 			return "minimal";
+ 		case WAL_LEVEL_ARCHIVE:
+ 			return "archive";
+ 		case WAL_LEVEL_HOT_STANDBY:
+ 			return "hot_standby";
+ 		case WAL_LEVEL_LOGICAL:
+ 			return "logical";
+ 	}
+ 	return _("unrecognized wal_level");
+ }
+ 
+ static void
+ get_controldata(void)
+ {
+ 	ControlFileData ControlFile;
+ 	int				fd;
+ 	char			ControlFilePath[MAXPGPATH];
+ 	pg_crc32c		crc;
+ 	time_t			time_tmp;
+ 	char			pgctime_str[128];
+ 	char			ckpttime_str[128];
+ 	char			sysident_str[32];
+ 	const char	   *strftime_fmt = "%c";
+ 	XLogSegNo		segno;
+ 	char			xlogfilename[MAXFNAMELEN];
+ 	StringInfoData	buf;
+ 
+ 	snprintf(ControlFilePath, MAXPGPATH, "%s/global/pg_control", DataDir);
+ 
+ 	if ((fd = open(ControlFilePath, O_RDONLY | PG_BINARY, 0)) == -1)
+ 		elog(ERROR, "could not open file \"%s\" for reading: %s",
+ 					 ControlFilePath, strerror(errno));
+ 
+ 	if (read(fd, &ControlFile, sizeof(ControlFileData)) != sizeof(ControlFileData))
+ 		elog(ERROR, "could not read file \"%s\": %s",
+ 					 ControlFilePath, strerror(errno));
+ 
+ 	close(fd);
+ 
+ 	/* Check the CRC. */
+ 	INIT_CRC32C(crc);
+ 	COMP_CRC32C(crc,
+ 			   (char *) &ControlFile,
+ 			   offsetof(ControlFileData, crc));
+ 	FIN_CRC32C(crc);
+ 
+ 	if (!EQ_CRC32C(crc, ControlFile.crc))
+ 		elog(ERROR, "calculated CRC checksum does not match value stored in file");
+ 
+ 	/*
+ 	 * This slightly-chintzy coding will work as long as the control file
+ 	 * timestamps are within the range of time_t; that should be the case in
+ 	 * all foreseeable circumstances, so we don't bother importing the
+ 	 * backend's timezone library.
+ 	 *
+ 	 * Use variable for format to suppress overly-anal-retentive gcc warning
+ 	 * about %c
+ 	 */
+ 	time_tmp = (time_t) ControlFile.time;
+ 	strftime(pgctime_str, sizeof(pgctime_str), strftime_fmt,
+ 			 localtime(&time_tmp));
+ 	time_tmp = (time_t) ControlFile.checkPointCopy.time;
+ 	strftime(ckpttime_str, sizeof(ckpttime_str), strftime_fmt,
+ 			 localtime(&time_tmp));
+ 
+ 	/*
+ 	 * Calculate name of the WAL file containing the latest checkpoint's REDO
+ 	 * start point.
+ 	 */
+ 	XLByteToSeg(ControlFile.checkPointCopy.redo, segno);
+ 	XLogFileName(xlogfilename, ControlFile.checkPointCopy.ThisTimeLineID, segno);
+ 
+ 	/*
+ 	 * Format system_identifier separately to keep platform-dependent format
+ 	 * code out of the translatable message string.
+ 	 */
+ 	snprintf(sysident_str, sizeof(sysident_str), UINT64_FORMAT,
+ 			 ControlFile.system_identifier);
+ 
+ 	initStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%u", ControlFile.pg_control_version);
+ 	ControlData[0].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%u", ControlFile.catalog_version_no);
+ 	ControlData[1].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%s", sysident_str);
+ 	ControlData[2].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%s", dbState(ControlFile.state));
+ 	ControlData[3].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%s", pgctime_str);
+ 	ControlData[4].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%X/%X",
+ 					 (uint32) (ControlFile.checkPoint >> 32),
+ 					 (uint32) ControlFile.checkPoint);
+ 	ControlData[5].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%X/%X",
+ 					 (uint32) (ControlFile.prevCheckPoint >> 32),
+ 					 (uint32) ControlFile.prevCheckPoint);
+ 	ControlData[6].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%X/%X",
+ 					 (uint32) (ControlFile.checkPointCopy.redo >> 32),
+ 					 (uint32) ControlFile.checkPointCopy.redo);
+ 	ControlData[7].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%s", xlogfilename);
+ 	ControlData[8].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%u", ControlFile.checkPointCopy.ThisTimeLineID);
+ 	ControlData[9].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%u", ControlFile.checkPointCopy.PrevTimeLineID);
+ 	ControlData[10].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%s",
+ 					 ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
+ 	ControlData[11].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%u/%u",
+ 					 ControlFile.checkPointCopy.nextXidEpoch,
+ 					 ControlFile.checkPointCopy.nextXid);
+ 	ControlData[12].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%u", ControlFile.checkPointCopy.nextOid);
+ 	ControlData[13].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%u", ControlFile.checkPointCopy.nextMulti);
+ 	ControlData[14].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%u", ControlFile.checkPointCopy.nextMultiOffset);
+ 	ControlData[15].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%u", ControlFile.checkPointCopy.oldestXid);
+ 	ControlData[16].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%u", ControlFile.checkPointCopy.oldestXidDB);
+ 	ControlData[17].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%u", ControlFile.checkPointCopy.oldestActiveXid);
+ 	ControlData[18].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%u", ControlFile.checkPointCopy.oldestMulti);
+ 	ControlData[19].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%u", ControlFile.checkPointCopy.oldestMultiDB);
+ 	ControlData[20].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%u", ControlFile.checkPointCopy.oldestCommitTs);
+ 	ControlData[21].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%s", ckpttime_str);
+ 	ControlData[22].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%X/%X",
+ 					 (uint32) (ControlFile.unloggedLSN >> 32),
+ 					 (uint32) ControlFile.unloggedLSN);
+ 	ControlData[23].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%X/%X",
+ 					 (uint32) (ControlFile.minRecoveryPoint >> 32),
+ 					 (uint32) ControlFile.minRecoveryPoint);
+ 	ControlData[24].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%u", ControlFile.minRecoveryPointTLI);
+ 	ControlData[25].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%X/%X",
+ 					 (uint32) (ControlFile.backupStartPoint >> 32),
+ 					 (uint32) ControlFile.backupStartPoint);
+ 	ControlData[26].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%X/%X",
+ 					 (uint32) (ControlFile.backupEndPoint >> 32),
+ 					 (uint32) ControlFile.backupEndPoint);
+ 	ControlData[27].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%s",
+ 					 ControlFile.backupEndRequired ? _("yes") : _("no"));
+ 	ControlData[28].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%s", wal_level_str(ControlFile.wal_level));
+ 	ControlData[29].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%s",
+ 					 ControlFile.wal_log_hints ? _("on") : _("off"));
+ 	ControlData[30].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%d", ControlFile.MaxConnections);
+ 	ControlData[31].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%d", ControlFile.max_worker_processes);
+ 	ControlData[32].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%d", ControlFile.max_prepared_xacts);
+ 	ControlData[33].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%d", ControlFile.max_locks_per_xact);
+ 	ControlData[34].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%s",
+ 					 ControlFile.track_commit_timestamp ? _("on") : _("off"));
+ 	ControlData[35].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%u", ControlFile.maxAlign);
+ 	ControlData[36].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%u", ControlFile.blcksz);
+ 	ControlData[37].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%u", ControlFile.relseg_size);
+ 	ControlData[38].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%u", ControlFile.xlog_blcksz);
+ 	ControlData[39].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%u", ControlFile.xlog_seg_size);
+ 	ControlData[40].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%u", ControlFile.nameDataLen);
+ 	ControlData[41].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%u", ControlFile.indexMaxKeys);
+ 	ControlData[42].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%u", ControlFile.toast_max_chunk_size);
+ 	ControlData[43].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%u", ControlFile.loblksize);
+ 	ControlData[44].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%s",
+ 					 (ControlFile.enableIntTimes ? _("64-bit integers") : _("floating-point numbers")));
+ 	ControlData[45].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%s",
+ 					 (ControlFile.float4ByVal ? _("by value") : _("by reference")));
+ 	ControlData[46].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%s",
+ 					 (ControlFile.float8ByVal ? _("by value") : _("by reference")));
+ 	ControlData[47].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "%u", ControlFile.data_checksum_version);
+ 	ControlData[48].setting = pstrdup(buf.data);
+ 	resetStringInfo(&buf);
+ }
diff --git a/src/bin/pg_config/pg_config.c b/src/bin/pg_config/pg_config.c
index 9b1f95d..25efc71 100644
*** a/src/bin/pg_config/pg_config.c
--- b/src/bin/pg_config/pg_config.c
***************
*** 29,65 ****
  static const char *progname;
  static char mypath[MAXPGPATH];
  
- 
- /*
-  * This function cleans up the paths for use with either cmd.exe or Msys
-  * on Windows. We need them to use filenames without spaces, for which a
-  * short filename is the safest equivalent, eg:
-  *		C:/Progra~1/
-  */
- static void
- cleanup_path(char *path)
- {
- #ifdef WIN32
- 	char	   *ptr;
- 
- 	/*
- 	 * GetShortPathName() will fail if the path does not exist, or short names
- 	 * are disabled on this file system.  In both cases, we just return the
- 	 * original path.  This is particularly useful for --sysconfdir, which
- 	 * might not exist.
- 	 */
- 	GetShortPathName(path, path, MAXPGPATH - 1);
- 
- 	/* Replace '\' with '/' */
- 	for (ptr = path; *ptr; ptr++)
- 	{
- 		if (*ptr == '\\')
- 			*ptr = '/';
- 	}
- #endif
- }
- 
- 
  /*
   * For each piece of information known to pg_config, we define a subroutine
   * to print it.  This is probably overkill, but it avoids code duplication
--- 29,34 ----
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index ddf7c67..e0ba271 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DESCR("get an individual replication ori
*** 5331,5336 ****
--- 5331,5344 ----
  DATA(insert OID = 6014 ( pg_show_replication_origin_status PGNSP PGUID 12 1 100 0 0 f f f f f t v 0 0 2249 "" "{26,25,3220,3220}" "{o,o,o,o}" "{local_id, external_id, remote_lsn, local_lsn}" _null_ _null_ pg_show_replication_origin_status _null_ _null_ _null_ ));
  DESCR("get progress for all replication origins");
  
+ /* pg_config */
+ DATA(insert OID = 3300 (  pg_config	PGNSP PGUID 12 1 1000 0 0 f f f f t t i 0 0 2249 "" "{25,25}" "{o,o}" "{name,setting}" _null_ _null_ pg_config _null_ _null_ _null_ ));
+ DESCR("pg_config binary as a function");
+ 
+ /* pg_controldata */
+ DATA(insert OID = 3308 (  pg_controldata	PGNSP PGUID 12 1 1000 0 0 f f f f t t i 0 0 2249 "" "{25,25}" "{o,o}" "{name,setting}" _null_ _null_ pg_controldata _null_ _null_ _null_ ));
+ DESCR("pg_controldata binary as a function");
+ 
  /*
   * Symbolic values for provolatile column: these indicate whether the result
   * of a function is dependent *only* on the values of its explicit arguments,
diff --git a/src/include/port.h b/src/include/port.h
index 3787cbf..ab38f62 100644
*** a/src/include/port.h
--- b/src/include/port.h
*************** extern void join_path_components(char *r
*** 42,47 ****
--- 42,48 ----
  					 const char *head, const char *tail);
  extern void canonicalize_path(char *path);
  extern void make_native_path(char *path);
+ extern void cleanup_path(char *path);
  extern bool path_contains_parent_reference(const char *path);
  extern bool path_is_relative_and_below_cwd(const char *path);
  extern bool path_is_prefix_of_path(const char *path1, const char *path2);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index fc1679e..4a67a12 100644
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum set_config_by_name(PG_FUNCT
*** 1121,1126 ****
--- 1121,1132 ----
  extern Datum show_all_settings(PG_FUNCTION_ARGS);
  extern Datum show_all_file_settings(PG_FUNCTION_ARGS);
  
+ /* pg_config.c */
+ extern Datum pg_config(PG_FUNCTION_ARGS);
+ 
+ /* pg_controldata.c */
+ extern Datum pg_controldata(PG_FUNCTION_ARGS);
+ 
  /* rls.c */
  extern Datum row_security_active(PG_FUNCTION_ARGS);
  extern Datum row_security_active_name(PG_FUNCTION_ARGS);
diff --git a/src/port/path.c b/src/port/path.c
index d0f72df..7613666 100644
*** a/src/port/path.c
--- b/src/port/path.c
*************** make_native_path(char *filename)
*** 172,177 ****
--- 172,207 ----
  
  
  /*
+  * This function cleans up the paths for use with either cmd.exe or Msys
+  * on Windows. We need them to use filenames without spaces, for which a
+  * short filename is the safest equivalent, eg:
+  *		C:/Progra~1/
+  */
+ void
+ cleanup_path(char *path)
+ {
+ #ifdef WIN32
+ 	char	   *ptr;
+ 
+ 	/*
+ 	 * GetShortPathName() will fail if the path does not exist, or short names
+ 	 * are disabled on this file system.  In both cases, we just return the
+ 	 * original path.  This is particularly useful for --sysconfdir, which
+ 	 * might not exist.
+ 	 */
+ 	GetShortPathName(path, path, MAXPGPATH - 1);
+ 
+ 	/* Replace '\' with '/' */
+ 	for (ptr = path; *ptr; ptr++)
+ 	{
+ 		if (*ptr == '\\')
+ 			*ptr = '/';
+ 	}
+ #endif
+ }
+ 
+ 
+ /*
   * join_path_components - join two path components, inserting a slash
   *
   * We omit the slash if either given component is empty.
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 44c6740..85b4082 100644
*** a/src/test/regress/expected/rules.out
--- b/src/test/regress/expected/rules.out
*************** pg_available_extensions| SELECT e.name,
*** 1305,1310 ****
--- 1305,1316 ----
      e.comment
     FROM (pg_available_extensions() e(name, default_version, comment)
       LEFT JOIN pg_extension x ON ((e.name = x.extname)));
+ pg_config| SELECT pg_config.name,
+     pg_config.setting
+    FROM pg_config() pg_config(name, setting);
+ pg_controldata| SELECT pg_controldata.name,
+     pg_controldata.setting
+    FROM pg_controldata() pg_controldata(name, setting);
  pg_cursors| SELECT c.name,
      c.statement,
      c.is_holdable,
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to