From 7be4425d5f9c89f3fdd7946370efe3a20ec0d2a6 Mon Sep 17 00:00:00 2001
From: Craig Ringer <craig.ringer@2ndquadrant.com>
Date: Mon, 26 Oct 2020 12:53:16 +0800
Subject: [PATCH v1 1/3] Add PQlibInfo() to get runtime configuration of
 current libpq

The new PQlibInfo() function allows applications linked to libpq to determine
whether the current libpq is built with SSL/TLS, GSSAPI, thread safety etc at
runtime. This lets applications check that the libpq the dynamic linker picked
actually matches the libpq they were compiled against. It also provides a way
for applications to include more useful libpq diagnostic and descriptive
information in their own diagnostic output commands.

Applications can also use PQlibInfo() to obtain finer-grained intput into
runtime decisions about whether to use certain libpq features. It is already
possible for applications to use lazy dynamic linking (RTLD_LAZY) or runtime
dynamic linking (dlopen) and use the return value from PQlibVersion() to decide
whether to attempt to use certain API functions. The addition of PQlibInfo()
extends this capability to potentially allow applications to access
compile-time optional library features. The application can test for the
availablity of the optional libpq APIs, so there's no requirement for libpq to
be able to expose sensible stubs when the functionality isn't configured or
available.

For covenient diagnostic use the new function PQlibInfoPrint() prints all
keys and values from PQlibInfo() to stdout.
---
 doc/src/sgml/libpq.sgml              | 195 ++++++++++++++++++++++++++-
 src/interfaces/libpq/Makefile        |   1 +
 src/interfaces/libpq/exports.txt     |   2 +
 src/interfaces/libpq/libpq-fe.h      |   6 +-
 src/interfaces/libpq/libpq-version.c | 124 +++++++++++++++++
 src/interfaces/libpq/libpq-version.h |  46 +++++++
 6 files changed, 372 insertions(+), 2 deletions(-)
 create mode 100644 src/interfaces/libpq/libpq-version.c
 create mode 100644 src/interfaces/libpq/libpq-version.h

diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index de60281fcb..f0e2255d18 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -6251,6 +6251,199 @@ int PQlibVersion(void);
     </listitem>
    </varlistentry>
 
+   <varlistentry id="libpq-PQlibInfo">
+    <term><function>PQlibInfo</function><indexterm
+    ><primary>PQlibInfo</primary><seealso>PQlibVersion</seealso></indexterm></term>
+
+    <listitem>
+     <para>
+      Return an array of <structname>PGlibInfoEntry</structname> describing the
+      version and configuration of the currently loaded <filename>libpq</filename>.
+<synopsis>
+/* libpq-version.h */
+const PGlibInfoEntry * PQlibInfo(void);
+</synopsis>
+     </para>
+
+     <para>
+      The result of <function>PQlibInfo()</function> provides details about
+      the <filename>libpq</filename> that the application is actually linked
+      to at runtime. It exposes finer-grained information than is available
+      from <function>PQlibVersion()</function>.
+     </para>
+
+     <para>
+      Applications must include <filename>libpq-version.h</filename> to use
+      <function>PQlibInfo()</function>.
+     </para>
+
+     <para>
+      The return value is a pointer to a statically allocated array of
+      <structname>PGlibInfoEntry</structname>:
+      <variablelist id="libpq-struct-PGlibInfoEntry" xreflabel="struct PGlibInfoEntry">
+        <title><structname>PGlibInfoEntry</structname></title>
+        <varlistentry>
+          <term><type>enum PGlibInfoEntryKey</type> <structfield>key</structfield></term>
+          <listitem>
+            <para>
+              <xref linkend="libpq-enum-PGlibInfoEntryKey"/> identifying the entry, or
+              <literal>PGRES_LIBINFO_END_OF_LIST</literal> (0) for the end-of-array
+              sentinel value. The application should ignore entries with unrecognised
+              values for this field.
+            </para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><type>const char *</type> <structfield>key_name</structfield></term>
+          <listitem>
+            <para>String representation of <structfield>key</structfield></para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><type>const char *</type> <structfield>value_str</structfield></term>
+          <listitem>
+            <para>Printable string describing key's value. Should never be 0.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><type>bool</type> <structfield>has_intvalue</structfield></term>
+          <listitem>
+            <para>1 if the <structfield>value_int</structfield> is valid, otherwise 0.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><type>int</type> <structfield>value_str</structfield></term>
+          <listitem>
+            <para>Integer describing key's value, if <structfield>value_int</structfield> is true.</para>
+          </listitem>
+        </varlistentry>
+      </variablelist>
+      The array is terminated by an entry with <structfield>key</structfield> <literal>==</literal>
+      <literal>PGRES_LIBINFO_END_OF_LIST</literal> (0). The caller
+      <emphasis>must not</emphasis> attempt to free or modify the return value.
+     </para>
+
+     <para>
+      Entries include but are not limited to:
+      <variablelist id="libpq-enum-PGlibInfoEntryKey" xreflabel="enum PGlibInfoEntryKey">
+        <title><type>PGlibInfoEntryKey</type></title>
+        <varlistentry>
+         <term><literal>PGRES_LIBINFO_END_OF_LIST</literal></term>
+         <listitem><para>List terminator. Do not read past this point. Always has value 0.</para></listitem>
+        </varlistentry>
+
+        <varlistentry>
+         <term><literal>PGRES_LIBINFO_END_OF_LIST</literal></term>
+         <listitem><para>List terminator. Do not read past this point. Always has value 0.</para></listitem>
+        </varlistentry>
+
+        <varlistentry>
+         <term><literal>PGRES_LIBINFO_VERSION_NUM</literal></term>
+         <listitem><para>Same as <function>PQlibVersion()</function></para></listitem>
+        </varlistentry>
+
+        <varlistentry>
+         <term><literal>PGRES_LIBINFO_VERSION_STR</literal></term>
+         <listitem><para>
+           Version string for the PostgreSQL this libpq came from, in exactly
+           the same format as the <literal>PG_VERSION_STR</literal> macro or
+           <xref linkend="guc-server-version-num"/> server variable. This
+           value is the version of the actually loaded libpq, not
+           the the application was compiled against or the version of
+           the postgres server being connected to.
+         </para></listitem>
+        </varlistentry>
+
+        <varlistentry>
+         <term><literal>PGRES_LIBINFO_CONFIGURE_ARGS</literal></term>
+         <listitem><para>
+           Arguments passed to the <literal>configure</literal>
+           utility when the PostgreSQL this libpq came from was compiled.
+         </para></listitem>
+        </varlistentry>
+
+        <varlistentry>
+         <term><literal>PGRES_LIBINFO_USE_SSL</literal></term>
+         <listitem><para>
+           1 if this libpq was compiled with SSL/TLS support, otherwise 0.
+         </para></listitem>
+        </varlistentry>
+
+        <varlistentry>
+         <term><literal>PGRES_LIBINFO_ENABLE_GSS</literal></term>
+         <listitem><para>
+           1 if this libpq was compiled with GSSAPI (Kerberos) support, otherwise 0.
+         </para></listitem>
+        </varlistentry>
+
+        <varlistentry>
+         <term><literal>PGRES_LIBINFO_ENABLE_THREAD_SAFETY</literal></term>
+         <listitem><para>
+           1 if this libpq was compiled with thread safety enabled, otherwise 0,
+           like <xref linkend="libpq-PQisthreadsafe"/>.
+         </para></listitem>
+        </varlistentry>
+
+        <varlistentry>
+         <term><literal>PGRES_LIBINFO_HAVE_UNIX_SOCKETS</literal></term>
+         <listitem><para>
+           1 if this libpq was compiled with unix socket support, otherwise 0.
+         </para></listitem>
+        </varlistentry>
+
+        <varlistentry>
+         <term><literal>PGRES_LIBINFO_DEFAULT_PGSOCKET_DIR</literal></term>
+         <listitem><para>
+           The default unix socket directory this libpq will use when no
+           <literal>host</literal> is provided in a connection string.
+         </para></listitem>
+        </varlistentry>
+
+        <varlistentry>
+         <term><literal>PGRES_LIBINFO_DEF_PGPORT</literal></term>
+         <listitem><para>
+           The default TCP or unix socket port that libpq will use if no
+           <literal>port</literal> is provided in a connection string.
+         </para></listitem>
+        </varlistentry>
+
+      </variablelist>
+     </para>
+
+     <note>
+      <para>
+       This function appeared in <productname>PostgreSQL</productname> version 14.
+      </para>
+     </note>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry id="libpq-PQlibInfoPrint">
+    <term><function>PQlibInfoPrint</function><indexterm
+    ><primary>PQlibInfoPrint</primary><seealso>PQlibInfo</seealso></indexterm></term>
+    <listitem>
+     <para>
+      Writes libpq version and configuration information to stdout.
+<synopsis>
+/* libpq-version.h */
+void PQlibInfoPrint(void);
+</synopsis>
+     </para>
+     <para>
+      This function prints the <structfield>key_str</structfield> and
+      <structfield>value_str</structfield> of each
+      <xref linkend="libpq-struct-PGlibInfoEntry"/>
+      returned by <xref linkend="libpq-PQlibInfo"/> to the application's
+      default stdio output stream (stdout).
+     </para>
+     <note>
+      <para>
+       This function appeared in <productname>PostgreSQL</productname> version 14.
+      </para>
+     </note>
+    </listitem>
+   </varlistentry>
+
   </variablelist>
 
  </sect1>
@@ -7998,7 +8191,7 @@ void PQinitSSL(int do_ssl);
   </para>
 
   <variablelist>
-   <varlistentry id="libpq-PQisthreadsafe">
+   <varlistentry id="libpq-PQisthreadsafe" xreflabel="PQisthreadsafe()">
     <term><function>PQisthreadsafe</function><indexterm><primary>PQisthreadsafe</primary></indexterm></term>
 
     <listitem>
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 4ac5f4b340..8a9a6f4c09 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -42,6 +42,7 @@ OBJS = \
 	fe-secure.o \
 	legacy-pqsignal.o \
 	libpq-events.o \
+	libpq-version.o \
 	pqexpbuffer.o \
 	fe-auth.o
 
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index bbc1f90481..1d8600f0d8 100644
--- a/src/interfaces/libpq/exports.txt
+++ b/src/interfaces/libpq/exports.txt
@@ -179,3 +179,5 @@ PQgetgssctx               176
 PQsetSSLKeyPassHook_OpenSSL         177
 PQgetSSLKeyPassHook_OpenSSL         178
 PQdefaultSSLKeyPassHook_OpenSSL     179
+PQlibInfo                           180
+PQlibInfoPrint                      181
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 3b6a9fbce3..d9594cfa9b 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -594,7 +594,11 @@ extern int	lo_export(PGconn *conn, Oid lobjId, const char *filename);
 
 /* === in fe-misc.c === */
 
-/* Get the version of the libpq library in use */
+/*
+ * Get the version of the libpq library in use (PG_VERSION_NUM).
+ *
+ * See libpq-version.h for more detailed library information.
+ */
 extern int	PQlibVersion(void);
 
 /* Determine length of multibyte encoded char at *s */
diff --git a/src/interfaces/libpq/libpq-version.c b/src/interfaces/libpq/libpq-version.c
new file mode 100644
index 0000000000..51a5c608c8
--- /dev/null
+++ b/src/interfaces/libpq/libpq-version.c
@@ -0,0 +1,124 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-version.c
+ *	  Functions to report libpq version, configuration and options
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/interfaces/libpq/fe-version.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres_fe.h"
+
+#include <stdio.h>
+
+#include "libpq-fe.h"
+#include "libpq-int.h"
+#include "libpq-version.h"
+
+/* Adapt defined(foo) to is_foo boolean-truth */
+#ifdef USE_SSL
+#define is_use_ssl 1
+#else
+#define is_use_ssl 0
+#endif
+#ifdef ENABLE_GSS
+#define is_enable_gss 1
+#else
+#define is_enable_gss 0
+#endif
+#ifdef ENABLE_THREAD_SAFETY
+#define is_enable_thread_safety 1
+#else
+#define is_enable_thread_safety 0
+#endif
+#ifdef HAVE_UNIX_SOCKETS
+#define is_have_unix_sockets 1
+#else
+#define is_have_unix_sockets 0
+#ifndef DEFAULT_PGSOCKET_DIR
+#define DEFAULT_PGSOCKET_DIR gettext_noop("")
+#endif
+#endif
+
+#define libinfo_boolstr(x) (x) == 1 ? gettext_noop("1") : gettext_noop("0")
+
+static const PGlibInfoEntry PGlibInfoEntries[] = {
+	{
+		PGRES_LIBINFO_VERSION_NUM,
+		gettext_noop("VERSION_NUM"),
+		CppAsString2(PG_VERSION_NUM), 1, PG_VERSION_NUM
+	},
+	{
+		PGRES_LIBINFO_VERSION_STR,
+		gettext_noop("VERSION"),
+		PG_VERSION_STR, 0, 0
+	},
+	{
+		PGRES_LIBINFO_CONFIGURE_ARGS,
+		gettext_noop("CONFIGURE_ARGS"),
+		CONFIGURE_ARGS, 0, 0
+	},
+	{
+		PGRES_LIBINFO_USE_SSL,
+		gettext_noop("USE_SSL"),
+		libinfo_boolstr(is_use_ssl), 1, is_use_ssl
+	},
+	{
+		PGRES_LIBINFO_ENABLE_GSS,
+		gettext_noop("ENABLE_GSS"),
+		libinfo_boolstr(is_enable_gss), 1, is_enable_gss
+	},
+	{
+		PGRES_LIBINFO_ENABLE_THREAD_SAFETY,
+		gettext_noop("ENABLE_THREAD_SAFETY"),
+		libinfo_boolstr(is_enable_thread_safety),
+		1, is_enable_thread_safety
+	},
+	{
+		PGRES_LIBINFO_HAVE_UNIX_SOCKETS,
+		gettext_noop("HAVE_UNIX_SOCKETS"),
+		libinfo_boolstr(is_have_unix_sockets),
+		1, is_have_unix_sockets
+	},
+	{
+		PGRES_LIBINFO_DEFAULT_PGSOCKET_DIR,
+		gettext_noop("DEFAULT_PGSOCKET_DIR"),
+		DEFAULT_PGSOCKET_DIR, 0, 0
+	},
+	{
+		PGRES_LIBINFO_DEF_PGPORT,
+		gettext_noop("DEF_PGPORT"),
+		DEF_PGPORT_STR, 1, DEF_PGPORT
+	},
+	/* Sentry value, must always be at end of list */
+	{ PGRES_LIBINFO_END_OF_LIST, NULL, 0, 0 }
+};
+
+/* Get key/value library configuration info for apps */
+const PGlibInfoEntry *
+PQlibInfo(void)
+{
+	return &PGlibInfoEntries[0];
+}
+
+/*
+ * Dump PGlibInfoEntries to stdout. This deliberately does not take a FILE*
+ * because it is not guaranteed to be safe to pass a FILE* across shared
+ * library boundaries on Windows or anywhere else that libpq might be linked to
+ * a different C runtime than the executable that loaded it .
+ */
+void
+PQlibInfoPrint(void)
+{
+	const PGlibInfoEntry * ientry = PQlibInfo();
+	while (ientry->key != PGRES_LIBINFO_END_OF_LIST)
+	{
+		fprintf(stdout, "%s: %s\n", ientry->key_name, ientry->value_str);
+		ientry ++;
+	}
+}
diff --git a/src/interfaces/libpq/libpq-version.h b/src/interfaces/libpq/libpq-version.h
new file mode 100644
index 0000000000..d38af8fa9a
--- /dev/null
+++ b/src/interfaces/libpq/libpq-version.h
@@ -0,0 +1,46 @@
+/*-------------------------------------------------------------------------
+ *
+ * libpq-version.h
+ *	  This file provides an interface to inspect the version and capabilities
+ *	  of the currently linked-to libpq at runtime.
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/interfaces/libpq/libpq-version.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "c.h"
+
+/* See the main documentation for info on these keys */
+typedef enum PGlibInfoEntryKey
+{
+	PGRES_LIBINFO_END_OF_LIST = 0,
+	PGRES_LIBINFO_VERSION_NUM,
+	PGRES_LIBINFO_VERSION_STR,
+	PGRES_LIBINFO_CONFIGURE_ARGS,
+	PGRES_LIBINFO_USE_SSL,
+	PGRES_LIBINFO_ENABLE_GSS,
+	PGRES_LIBINFO_ENABLE_THREAD_SAFETY,
+	PGRES_LIBINFO_HAVE_UNIX_SOCKETS,
+	PGRES_LIBINFO_DEFAULT_PGSOCKET_DIR,
+	PGRES_LIBINFO_DEF_PGPORT
+} PGlibInfoEntryKey;
+
+/* One entry from PQlibInfo() */
+typedef struct PGlibInfoEntry
+{
+	enum PGlibInfoEntryKey key;
+	const char * key_name;
+	const char * value_str;
+	bool has_int_value;
+	int value_int;
+} PGlibInfoEntry;
+
+/* Return statically allocated array of PQlibInfo terminated by null sentinel */
+extern const PGlibInfoEntry * PQlibInfo(void);
+
+/* Dump PQlibInfo output to stdout */
+extern void PQlibInfoPrint(void);
-- 
2.26.2

