v1-0001-extension_control_path.patch

text/plain

Filename: v1-0001-extension_control_path.patch
Type: text/plain
Part: 0
Message: Re: RFC: Additional Directory for Extensions

Patch

Format: format-patch
Series: patch v1-0001
Subject: extension_control_path
File+
doc/src/sgml/config.sgml 68 0
doc/src/sgml/extend.sgml 14 5
doc/src/sgml/ref/create_extension.sgml 4 2
src/backend/commands/extension.c 66 21
src/backend/utils/fmgr/dfmgr.c 48 28
src/backend/utils/misc/guc_tables.c 13 0
src/backend/utils/misc/postgresql.conf.sample 1 0
src/include/commands/extension.h 2 0
src/include/fmgr.h 2 0
src/Makefile.global.in 12 7
From fdf1bd8abbb9aaff3747c57d653ef19f8be31fe8 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Thu, 5 Dec 2024 11:49:05 +0100
Subject: [PATCH v1] extension_control_path

The new GUC extension_control_path specifies a path to look for
extension control files.  The default value is $system, which looks in
the compiled-in location, as before.

The path search uses the same code and works in the same way as
dynamic_library_path.

Discussion: https://www.postgresql.org/message-id/flat/E7C7BFFB-8857-48D4-A71F-88B359FADCFD@justatheory.com

TODO: Some utility functions such as pg_available_extensions() are not
adjusted to be aware of the path yet.
---
 doc/src/sgml/config.sgml                      | 68 +++++++++++++++
 doc/src/sgml/extend.sgml                      | 19 ++--
 doc/src/sgml/ref/create_extension.sgml        |  6 +-
 src/Makefile.global.in                        | 19 ++--
 src/backend/commands/extension.c              | 87 ++++++++++++++-----
 src/backend/utils/fmgr/dfmgr.c                | 76 ++++++++++------
 src/backend/utils/misc/guc_tables.c           | 13 +++
 src/backend/utils/misc/postgresql.conf.sample |  1 +
 src/include/commands/extension.h              |  2 +
 src/include/fmgr.h                            |  2 +
 10 files changed, 230 insertions(+), 63 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index e0c8325a39c..94ef9fb44d7 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -10476,6 +10476,74 @@ <title>Other Defaults</title>
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-extension-control-path" xreflabel="extension_control_path">
+      <term><varname>extension_control_path</varname> (<type>string</type>)
+      <indexterm>
+       <primary><varname>extension_control_path</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        A path to search for extensions, specifically extension control files
+        (<filename><replaceable>name</replaceable>.control</filename>).  The
+        remaining extension script and secondary control files are then loaded
+        from the same directory where the primary control file was found.
+        See <xref linkend="extend-extensions-files"/> for details.
+       </para>
+
+       <para>
+        The value for <varname>extension_control_path</varname> must be a
+        list of absolute directory paths separated by colons (or semi-colons
+        on Windows).  If a list element starts
+        with the special string <literal>$system</literal>, the
+        compiled-in <productname>PostgreSQL</productname> extension
+        directory is substituted for <literal>$system</literal>; this
+        is where the extensions provided by the standard
+        <productname>PostgreSQL</productname> distribution are installed.
+        (Use <literal>pg_config --sharedir</literal> to find out the name of
+        this directory.) For example:
+<programlisting>
+extension_control_path = '/usr/local/share/postgresql/extension:/home/my_project/share/extension:$system'
+</programlisting>
+        or, in a Windows environment:
+<programlisting>
+extension_control_path = 'C:\tools\postgresql\extension;H:\my_project\share\extension;$system'
+</programlisting>
+        Note that the path elements should typically end in
+        <literal>extension</literal> if the normal installation layouts are
+        followed.  (The value for <literal>$system</literal> already includes
+        the <literal>extension</literal> suffix.)
+       </para>
+
+       <para>
+        The default value for this parameter is
+        <literal>'$system'</literal>. If the value is set to an empty
+        string, the default <literal>'$system'</literal> is also assumed.
+       </para>
+
+       <para>
+        This parameter can be changed at run time by superusers and users
+        with the appropriate <literal>SET</literal> privilege, but a
+        setting done that way will only persist until the end of the
+        client connection, so this method should be reserved for
+        development purposes. The recommended way to set this parameter
+        is in the <filename>postgresql.conf</filename> configuration
+        file.
+       </para>
+
+       <para>
+        Note that if you set this parameter to be able to load extensions from
+        nonstandard locations, you will most likely also need to set <xref
+        linkend="guc-dynamic-library-path"/> to a correspondent location, for
+        example,
+<programlisting>
+extension_control_path = '/usr/local/share/postgresql/extension:$system'
+dynamic_library_path = '/usr/local/lib/postgresql:$libdir'
+</programlisting>
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="guc-gin-fuzzy-search-limit" xreflabel="gin_fuzzy_search_limit">
       <term><varname>gin_fuzzy_search_limit</varname> (<type>integer</type>)
       <indexterm>
diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index 218940ee5ce..0a20d06abfb 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -649,6 +649,11 @@ <title>Extension Files</title>
      control file can specify a different directory for the script file(s).
     </para>
 
+    <para>
+     Additional locations for extension control files can be configured using
+     the parameter <xref linkend="guc-extension-control-path"/>.
+    </para>
+
     <para>
      The file format for an extension control file is the same as for the
      <filename>postgresql.conf</filename> file, namely a list of
@@ -669,9 +674,9 @@ <title>Extension Files</title>
        <para>
         The directory containing the extension's <acronym>SQL</acronym> script
         file(s).  Unless an absolute path is given, the name is relative to
-        the installation's <literal>SHAREDIR</literal> directory.  The
-        default behavior is equivalent to specifying
-        <literal>directory = 'extension'</literal>.
+        the installation's <literal>SHAREDIR</literal> directory.  By default,
+        the script files are looked for in the same directory where the
+        control file was found.
        </para>
       </listitem>
      </varlistentry>
@@ -719,8 +724,8 @@ <title>Extension Files</title>
        <para>
         The value of this parameter will be substituted for each occurrence
         of <literal>MODULE_PATHNAME</literal> in the script file(s).  If it is not
-        set, no substitution is made.  Typically, this is set to
-        <literal>$libdir/<replaceable>shared_library_name</replaceable></literal> and
+        set, no substitution is made.  Typically, this is set to just
+        <literal><replaceable>shared_library_name</replaceable></literal> and
         then <literal>MODULE_PATHNAME</literal> is used in <command>CREATE
         FUNCTION</command> commands for C-language functions, so that the script
         files do not need to hard-wire the name of the shared library.
@@ -1808,6 +1813,10 @@ <title>Extension Building Infrastructure</title>
     setting <varname>PG_CONFIG</varname> to point to its
     <command>pg_config</command> program, either within the makefile
     or on the <literal>make</literal> command line.
+    You can also select a separate installation directory for your extension
+    by setting the <literal>make</literal> variable <varname>prefix</varname>
+    on the <literal>make</literal> command line.  (But this will then require
+    additional setup to get the server to find the extension there.)
    </para>
 
    <para>
diff --git a/doc/src/sgml/ref/create_extension.sgml b/doc/src/sgml/ref/create_extension.sgml
index ca2b80d669c..713abd9c494 100644
--- a/doc/src/sgml/ref/create_extension.sgml
+++ b/doc/src/sgml/ref/create_extension.sgml
@@ -90,8 +90,10 @@ <title>Parameters</title>
        <para>
         The name of the extension to be
         installed. <productname>PostgreSQL</productname> will create the
-        extension using details from the file
-        <literal>SHAREDIR/extension/</literal><replaceable class="parameter">extension_name</replaceable><literal>.control</literal>.
+        extension using details from the file <filename><replaceable
+        class="parameter">extension_name</replaceable>.control</filename>,
+        found via the server's extension control path (set by <xref
+        linkend="guc-extension-control-path"/>.)
        </para>
       </listitem>
      </varlistentry>
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index eac3d001211..7c1bffbea07 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -87,9 +87,19 @@ endif # not PGXS
 #
 # In a PGXS build, we cannot use the values inserted into Makefile.global
 # by configure, since the installation tree may have been relocated.
-# Instead get the path values from pg_config.
+# Instead get the path values from pg_config.  But users can specify
+# prefix explicitly, if they want to select their own installation
+# location.
 
-ifndef PGXS
+ifdef PGXS
+# Extension makefiles should set PG_CONFIG, but older ones might not
+ifndef PG_CONFIG
+PG_CONFIG = pg_config
+endif
+endif
+
+# This means: if ((not PGXS) or prefix)
+ifneq (,$(if $(PGXS),,1)$(prefix))
 
 # Note that prefix, exec_prefix, and datarootdir aren't defined in a PGXS build;
 # makefiles may only use the derived variables such as bindir.
@@ -147,11 +157,6 @@ localedir := @localedir@
 
 else # PGXS case
 
-# Extension makefiles should set PG_CONFIG, but older ones might not
-ifndef PG_CONFIG
-PG_CONFIG = pg_config
-endif
-
 bindir := $(shell $(PG_CONFIG) --bindir)
 datadir := $(shell $(PG_CONFIG) --sharedir)
 sysconfdir := $(shell $(PG_CONFIG) --sysconfdir)
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index af6bd8ff426..9115a48d25f 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -69,6 +69,9 @@
 #include "utils/varlena.h"
 
 
+/* GUC */
+char	   *Extension_control_path;
+
 /* Globally visible state variables */
 bool		creating_extension = false;
 Oid			CurrentExtensionObject = InvalidOid;
@@ -79,6 +82,7 @@ Oid			CurrentExtensionObject = InvalidOid;
 typedef struct ExtensionControlFile
 {
 	char	   *name;			/* name of the extension */
+	char	   *control_dir;	/* directory where control file was found */
 	char	   *directory;		/* directory for script files */
 	char	   *default_version;	/* default install target version, if any */
 	char	   *module_pathname;	/* string to substitute for
@@ -328,6 +332,12 @@ is_extension_script_filename(const char *filename)
 	return (extension != NULL) && (strcmp(extension, ".sql") == 0);
 }
 
+/*
+ * TODO
+ *
+ * This is now only for finding/listing available extensions.  Rewrite to use
+ * path.  See further TODOs below.
+ */
 static char *
 get_extension_control_directory(void)
 {
@@ -341,16 +351,45 @@ get_extension_control_directory(void)
 	return result;
 }
 
+/*
+ * Find control file for extension with name in control->name, looking in the
+ * path.  Return the full file name, or NULL if not found.  If found, the
+ * directory is recorded in control->control_dir.
+ */
 static char *
-get_extension_control_filename(const char *extname)
+find_extension_control_filename(ExtensionControlFile *control)
 {
 	char		sharepath[MAXPGPATH];
+	char	   *system_dir;
+	char	   *basename;
+	char	   *ecp;
 	char	   *result;
 
+	Assert(control->name);
+
 	get_share_path(my_exec_path, sharepath);
-	result = (char *) palloc(MAXPGPATH);
-	snprintf(result, MAXPGPATH, "%s/extension/%s.control",
-			 sharepath, extname);
+	system_dir = psprintf("%s/extension", sharepath);
+
+	basename = psprintf("%s.control", control->name);
+
+	/*
+	 * find_in_path() does nothing if the path value is empty.  This is the
+	 * historical behavior for dynamic_library_path, but it makes no sense for
+	 * extensions.  So in that case, substitute a default value.
+	 */
+	ecp = Extension_control_path;
+	if (strlen(ecp) == 0)
+		ecp = "$system";
+	result = find_in_path(basename, Extension_control_path, "extension_control_path", "$system", system_dir);
+
+	if (result)
+	{
+		const char *p;
+
+		p = strrchr(result, '/');
+		Assert(p);
+		control->control_dir = pnstrdup(result, p - result);
+	}
 
 	return result;
 }
@@ -366,7 +405,7 @@ get_extension_script_directory(ExtensionControlFile *control)
 	 * installation's share directory.
 	 */
 	if (!control->directory)
-		return get_extension_control_directory();
+		return pstrdup(control->control_dir);
 
 	if (is_absolute_path(control->directory))
 		return pstrdup(control->directory);
@@ -444,27 +483,25 @@ parse_extension_control_file(ExtensionControlFile *control,
 	if (version)
 		filename = get_extension_aux_control_filename(control, version);
 	else
-		filename = get_extension_control_filename(control->name);
+		filename = find_extension_control_filename(control);
+
+	if (!filename)
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("extension \"%s\" is not available", control->name),
+				 errhint("The extension must first be installed on the system where PostgreSQL is running.")));
+	}
 
 	if ((file = AllocateFile(filename, "r")) == NULL)
 	{
-		if (errno == ENOENT)
+		/* no complaint for missing auxiliary file */
+		if (errno == ENOENT && version)
 		{
-			/* no complaint for missing auxiliary file */
-			if (version)
-			{
-				pfree(filename);
-				return;
-			}
-
-			/* missing control file indicates extension is not installed */
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("extension \"%s\" is not available", control->name),
-					 errdetail("Could not open extension control file \"%s\": %m.",
-							   filename),
-					 errhint("The extension must first be installed on the system where PostgreSQL is running.")));
+			pfree(filename);
+			return;
 		}
+
 		ereport(ERROR,
 				(errcode_for_file_access(),
 				 errmsg("could not open extension control file \"%s\": %m",
@@ -2114,6 +2151,8 @@ RemoveExtensionById(Oid extId)
  * The system view pg_available_extensions provides a user interface to this
  * SRF, adding information about whether the extensions are installed in the
  * current DB.
+ *
+ * TODO: make aware of path
  */
 Datum
 pg_available_extensions(PG_FUNCTION_ARGS)
@@ -2194,6 +2233,8 @@ pg_available_extensions(PG_FUNCTION_ARGS)
  * The system view pg_available_extension_versions provides a user interface
  * to this SRF, adding information about which versions are installed in the
  * current DB.
+ *
+ * TODO: make aware of path
  */
 Datum
 pg_available_extension_versions(PG_FUNCTION_ARGS)
@@ -2366,6 +2407,8 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
  * directory.  That's not a bulletproof check, since the file might be
  * invalid, but this is only used for hints so it doesn't have to be 100%
  * right.
+ *
+ * TODO: make aware of path
  */
 bool
 extension_file_exists(const char *extensionName)
@@ -2445,6 +2488,8 @@ convert_requires_to_datum(List *requires)
 /*
  * This function reports the version update paths that exist for the
  * specified extension.
+ *
+ * TODO: make aware of path
  */
 Datum
 pg_extension_update_paths(PG_FUNCTION_ARGS)
diff --git a/src/backend/utils/fmgr/dfmgr.c b/src/backend/utils/fmgr/dfmgr.c
index 8e81ecc7491..2372d0944c0 100644
--- a/src/backend/utils/fmgr/dfmgr.c
+++ b/src/backend/utils/fmgr/dfmgr.c
@@ -71,8 +71,7 @@ static void incompatible_module_error(const char *libname,
 									  const Pg_magic_struct *module_magic_data) pg_attribute_noreturn();
 static char *expand_dynamic_library_name(const char *name);
 static void check_restricted_library_name(const char *name);
-static char *substitute_libpath_macro(const char *name);
-static char *find_in_dynamic_libpath(const char *basename);
+static char *substitute_path_macro(const char *str, const char *macro, const char *value);
 
 /* Magic structure that module needs to match to be accepted */
 static const Pg_magic_struct magic_data = PG_MODULE_MAGIC_DATA;
@@ -398,7 +397,7 @@ incompatible_module_error(const char *libname,
 /*
  * If name contains a slash, check if the file exists, if so return
  * the name.  Else (no slash) try to expand using search path (see
- * find_in_dynamic_libpath below); if that works, return the fully
+ * find_in_path below); if that works, return the fully
  * expanded file name.  If the previous failed, append DLSUFFIX and
  * try again.  If all fails, just return the original name.
  *
@@ -413,17 +412,25 @@ expand_dynamic_library_name(const char *name)
 
 	Assert(name);
 
+	/*
+	 * If the value starts with "$libdir/", strip that.  This is because many
+	 * extensions have hardcoded '$libdir/foo' as their library name, which
+	 * prevents using the path.
+	 */
+	if (strncmp(name, "$libdir/", 8) == 0)
+		name += 8;
+
 	have_slash = (first_dir_separator(name) != NULL);
 
 	if (!have_slash)
 	{
-		full = find_in_dynamic_libpath(name);
+		full = find_in_path(name, Dynamic_library_path, "dynamic_library_path", "$libdir", pkglib_path);
 		if (full)
 			return full;
 	}
 	else
 	{
-		full = substitute_libpath_macro(name);
+		full = substitute_path_macro(name, "$libdir", pkglib_path);
 		if (pg_file_exists(full))
 			return full;
 		pfree(full);
@@ -433,14 +440,14 @@ expand_dynamic_library_name(const char *name)
 
 	if (!have_slash)
 	{
-		full = find_in_dynamic_libpath(new);
+		full = find_in_path(new, Dynamic_library_path, "dynamic_library_path", "$libdir", pkglib_path);
 		pfree(new);
 		if (full)
 			return full;
 	}
 	else
 	{
-		full = substitute_libpath_macro(new);
+		full = substitute_path_macro(new, "$libdir", pkglib_path);
 		pfree(new);
 		if (pg_file_exists(full))
 			return full;
@@ -475,47 +482,60 @@ check_restricted_library_name(const char *name)
  * Result is always freshly palloc'd.
  */
 static char *
-substitute_libpath_macro(const char *name)
+substitute_path_macro(const char *str, const char *macro, const char *value)
 {
 	const char *sep_ptr;
 
-	Assert(name != NULL);
+	Assert(str != NULL);
+	Assert(macro[0] == '$');
 
-	/* Currently, we only recognize $libdir at the start of the string */
-	if (name[0] != '$')
-		return pstrdup(name);
+	/* Currently, we only recognize $macro at the start of the string */
+	if (str[0] != '$')
+		return pstrdup(str);
 
-	if ((sep_ptr = first_dir_separator(name)) == NULL)
-		sep_ptr = name + strlen(name);
+	if ((sep_ptr = first_dir_separator(str)) == NULL)
+		sep_ptr = str + strlen(str);
 
-	if (strlen("$libdir") != sep_ptr - name ||
-		strncmp(name, "$libdir", strlen("$libdir")) != 0)
+	if (strlen(macro) != sep_ptr - str ||
+		strncmp(str, macro, strlen(macro)) != 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_NAME),
-				 errmsg("invalid macro name in dynamic library path: %s",
-						name)));
+				 errmsg("invalid macro name in path: %s",
+						str)));
 
-	return psprintf("%s%s", pkglib_path, sep_ptr);
+	return psprintf("%s%s", value, sep_ptr);
 }
 
 
 /*
  * Search for a file called 'basename' in the colon-separated search
- * path Dynamic_library_path.  If the file is found, the full file name
+ * path given.  If the file is found, the full file name
  * is returned in freshly palloc'd memory.  If the file is not found,
  * return NULL.
+ *
+ * path_param is the name of the parameter that path came from, for error
+ * messages.
+ *
+ * macro and macro_val allow substituting a macro; see
+ * substitute_path_macro().
  */
-static char *
-find_in_dynamic_libpath(const char *basename)
+char *
+find_in_path(const char *basename, const char *path, const char *path_param,
+			 const char *macro, const char *macro_val)
 {
 	const char *p;
 	size_t		baselen;
 
 	Assert(basename != NULL);
 	Assert(first_dir_separator(basename) == NULL);
-	Assert(Dynamic_library_path != NULL);
+	Assert(path != NULL);
+	Assert(path_param != NULL);
+
+	p = path;
 
-	p = Dynamic_library_path;
+	/*
+	 * If the path variable is empty, don't do a path search.
+	 */
 	if (strlen(p) == 0)
 		return NULL;
 
@@ -532,7 +552,7 @@ find_in_dynamic_libpath(const char *basename)
 		if (piece == p)
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_NAME),
-					 errmsg("zero-length component in parameter \"dynamic_library_path\"")));
+					 errmsg("zero-length component in parameter \"%s\"", path_param)));
 
 		if (piece == NULL)
 			len = strlen(p);
@@ -542,7 +562,7 @@ find_in_dynamic_libpath(const char *basename)
 		piece = palloc(len + 1);
 		strlcpy(piece, p, len + 1);
 
-		mangled = substitute_libpath_macro(piece);
+		mangled = substitute_path_macro(piece, macro, macro_val);
 		pfree(piece);
 
 		canonicalize_path(mangled);
@@ -551,13 +571,13 @@ find_in_dynamic_libpath(const char *basename)
 		if (!is_absolute_path(mangled))
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_NAME),
-					 errmsg("component in parameter \"dynamic_library_path\" is not an absolute path")));
+					 errmsg("component in parameter \"%s\" is not an absolute path", path_param)));
 
 		full = palloc(strlen(mangled) + 1 + baselen + 1);
 		sprintf(full, "%s/%s", mangled, basename);
 		pfree(mangled);
 
-		elog(DEBUG3, "find_in_dynamic_libpath: trying \"%s\"", full);
+		elog(DEBUG3, "%s: trying \"%s\"", __func__, full);
 
 		if (pg_file_exists(full))
 			return full;
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 8cf1afbad20..5a280f62b77 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -39,6 +39,7 @@
 #include "catalog/namespace.h"
 #include "catalog/storage.h"
 #include "commands/async.h"
+#include "commands/extension.h"
 #include "commands/event_trigger.h"
 #include "commands/tablespace.h"
 #include "commands/trigger.h"
@@ -4252,6 +4253,18 @@ struct config_string ConfigureNamesString[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"extension_control_path", PGC_SUSET, CLIENT_CONN_OTHER,
+			gettext_noop("Sets the path for extension control files."),
+			gettext_noop("The remaining extension script and secondary control files are then loaded "
+						 "from the same directory where the primary control file was found."),
+			GUC_SUPERUSER_ONLY
+		},
+		&Extension_control_path,
+		"$system",
+		NULL, NULL, NULL
+	},
+
 	{
 		{"krb_server_keyfile", PGC_SIGHUP, CONN_AUTH_AUTH,
 			gettext_noop("Sets the location of the Kerberos server key file."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index a2ac7575ca7..83e72b33fb7 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -774,6 +774,7 @@
 # - Other Defaults -
 
 #dynamic_library_path = '$libdir'
+#extension_control_path = '$system'
 #gin_fuzzy_search_limit = 0
 
 
diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h
index c6f3f867eb7..fe8a97570ea 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -17,6 +17,8 @@
 #include "catalog/objectaddress.h"
 #include "parser/parse_node.h"
 
+/* GUC */
+extern PGDLLIMPORT char *Extension_control_path;
 
 /*
  * creating_extension is only true while running a CREATE EXTENSION or ALTER
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index 1e3795de4a8..2930b61cee5 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -740,6 +740,8 @@ extern bool CheckFunctionValidatorAccess(Oid validatorOid, Oid functionOid);
  */
 extern PGDLLIMPORT char *Dynamic_library_path;
 
+extern char *find_in_path(const char *basename, const char *path, const char *path_param,
+						  const char *macro, const char *macro_val);
 extern void *load_external_function(const char *filename, const char *funcname,
 									bool signalNotFound, void **filehandle);
 extern void *lookup_external_function(void *filehandle, const char *funcname);

base-commit: 71cb352904c1833fe067d6f191269710fe2ca06f
-- 
2.47.1