summaryrefslogtreecommitdiff
path: root/src/backend/commands
diff options
context:
space:
mode:
authorPeter Eisentraut2025-03-19 05:57:20 +0000
committerPeter Eisentraut2025-03-19 06:03:20 +0000
commit4f7f7b0375854e2f89876473405a8f21c95012af (patch)
treec413b2bd115ee78c960607db7986e206ddae96f1 /src/backend/commands
parent2cce0fe440fb3f252a7be70a89298168009a2c15 (diff)
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. Some use cases of this are: (1) testing extensions during package builds, (2) installing extensions outside security-restricted containers like Python.app (on macOS), (3) adding extensions to PostgreSQL running in a Kubernetes environment using operators such as CloudNativePG without having to rebuild the base image for each new extension. There is also a tweak in Makefile.global so that it is possible to install extensions using PGXS into an different directory than the default, using 'make install prefix=/else/where'. This previously only worked when specifying the subdirectories, like 'make install datadir=/else/where/share pkglibdir=/else/where/lib', for purely implementation reasons. (Of course, without the path feature, installing elsewhere was rarely useful.) Author: Peter Eisentraut <[email protected]> Co-authored-by: Matheus Alcantara <[email protected]> Reviewed-by: David E. Wheeler <[email protected]> Reviewed-by: Gabriele Bartolini <[email protected]> Reviewed-by: Marco Nenciarini <[email protected]> Reviewed-by: Niccolò Fei <[email protected]> Discussion: https://www.postgresql.org/message-id/flat/[email protected]
Diffstat (limited to 'src/backend/commands')
-rw-r--r--src/backend/commands/extension.c403
1 files changed, 261 insertions, 142 deletions
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index d9bb4ce5f1e..dc38c325770 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -54,6 +54,7 @@
#include "funcapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
+#include "nodes/pg_list.h"
#include "nodes/queryjumble.h"
#include "storage/fd.h"
#include "tcop/utility.h"
@@ -69,6 +70,9 @@
#include "utils/varlena.h"
+/* GUC */
+char *Extension_control_path;
+
/* Globally visible state variables */
bool creating_extension = false;
Oid CurrentExtensionObject = InvalidOid;
@@ -79,6 +83,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
@@ -146,6 +151,7 @@ static void ExecAlterExtensionContentsRecurse(AlterExtensionContentsStmt *stmt,
ObjectAddress extension,
ObjectAddress object);
static char *read_whole_file(const char *filename, int *length);
+static ExtensionControlFile *new_ExtensionControlFile(const char *extname);
/*
@@ -328,29 +334,104 @@ is_extension_script_filename(const char *filename)
return (extension != NULL) && (strcmp(extension, ".sql") == 0);
}
-static char *
-get_extension_control_directory(void)
+/*
+ * Return a list of directories declared on extension_control_path GUC.
+ */
+static List *
+get_extension_control_directories(void)
{
char sharepath[MAXPGPATH];
- char *result;
+ char *system_dir;
+ char *ecp;
+ List *paths = NIL;
get_share_path(my_exec_path, sharepath);
- result = (char *) palloc(MAXPGPATH);
- snprintf(result, MAXPGPATH, "%s/extension", sharepath);
- return result;
+ system_dir = psprintf("%s/extension", sharepath);
+
+ if (strlen(Extension_control_path) == 0)
+ {
+ paths = lappend(paths, system_dir);
+ }
+ else
+ {
+ /* Duplicate the string so we can modify it */
+ ecp = pstrdup(Extension_control_path);
+
+ for (;;)
+ {
+ int len;
+ char *mangled;
+ char *piece = first_path_var_separator(ecp);
+
+ /* Get the length of the next path on ecp */
+ if (piece == NULL)
+ len = strlen(ecp);
+ else
+ len = piece - ecp;
+
+ /* Copy the next path found on ecp */
+ piece = palloc(len + 1);
+ strlcpy(piece, ecp, len + 1);
+
+ /* Substitute the path macro if needed */
+ mangled = substitute_path_macro(piece, "$system", system_dir);
+ pfree(piece);
+
+ /* Canonicalize the path based on the OS and add to the list */
+ canonicalize_path(mangled);
+ paths = lappend(paths, mangled);
+
+ /* Break if ecp is empty or move to the next path on ecp */
+ if (ecp[len] == '\0')
+ break;
+ else
+ ecp += len + 1;
+ }
+ }
+
+ return paths;
}
+/*
+ * 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, ecp, "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 +447,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);
@@ -424,6 +505,11 @@ get_extension_script_filename(ExtensionControlFile *control,
* fields of *control. We parse primary file if version == NULL,
* else the optional auxiliary file for that version.
*
+ * The control file will be search on Extension_control_path paths if
+ * control->control_dir is NULL, otherwise it will use the value of control_dir
+ * to read and parse the .control file, so it assume that the control_dir is a
+ * valid path for the control file being parsed.
+ *
* Control files are supposed to be very short, half a dozen lines,
* so we don't worry about memory allocation risks here. Also we don't
* worry about what encoding it's in; all values are expected to be ASCII.
@@ -444,27 +530,35 @@ parse_extension_control_file(ExtensionControlFile *control,
if (version)
filename = get_extension_aux_control_filename(control, version);
else
- filename = get_extension_control_filename(control->name);
+ {
+ /*
+ * If control_dir is already set, use it, else do a path search.
+ */
+ if (control->control_dir)
+ {
+ filename = psprintf("%s/%s.control", control->control_dir, control->name);
+ }
+ else
+ 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",
@@ -603,17 +697,7 @@ parse_extension_control_file(ExtensionControlFile *control,
static ExtensionControlFile *
read_extension_control_file(const char *extname)
{
- ExtensionControlFile *control;
-
- /*
- * Set up default values. Pointer fields are initially null.
- */
- control = (ExtensionControlFile *) palloc0(sizeof(ExtensionControlFile));
- control->name = pstrdup(extname);
- control->relocatable = false;
- control->superuser = true;
- control->trusted = false;
- control->encoding = -1;
+ ExtensionControlFile *control = new_ExtensionControlFile(extname);
/*
* Parse the primary control file.
@@ -2121,68 +2205,74 @@ Datum
pg_available_extensions(PG_FUNCTION_ARGS)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
+ List *locations;
DIR *dir;
struct dirent *de;
/* Build tuplestore to hold the result rows */
InitMaterializedSRF(fcinfo, 0);
- location = get_extension_control_directory();
- dir = AllocateDir(location);
+ locations = get_extension_control_directories();
- /*
- * If the control directory doesn't exist, we want to silently return an
- * empty set. Any other error will be reported by ReadDir.
- */
- if (dir == NULL && errno == ENOENT)
+ foreach_ptr(char, location, locations)
{
- /* do nothing */
- }
- else
- {
- while ((de = ReadDir(dir, location)) != NULL)
+ dir = AllocateDir(location);
+
+ /*
+ * If the control directory doesn't exist, we want to silently return
+ * an empty set. Any other error will be reported by ReadDir.
+ */
+ if (dir == NULL && errno == ENOENT)
{
- ExtensionControlFile *control;
- char *extname;
- Datum values[3];
- bool nulls[3];
+ /* do nothing */
+ }
+ else
+ {
+ while ((de = ReadDir(dir, location)) != NULL)
+ {
+ ExtensionControlFile *control;
+ char *extname;
+ Datum values[3];
+ bool nulls[3];
- if (!is_extension_control_filename(de->d_name))
- continue;
+ if (!is_extension_control_filename(de->d_name))
+ continue;
- /* extract extension name from 'name.control' filename */
- extname = pstrdup(de->d_name);
- *strrchr(extname, '.') = '\0';
+ /* extract extension name from 'name.control' filename */
+ extname = pstrdup(de->d_name);
+ *strrchr(extname, '.') = '\0';
- /* ignore it if it's an auxiliary control file */
- if (strstr(extname, "--"))
- continue;
+ /* ignore it if it's an auxiliary control file */
+ if (strstr(extname, "--"))
+ continue;
- control = read_extension_control_file(extname);
+ control = new_ExtensionControlFile(extname);
+ control->control_dir = pstrdup(location);
+ parse_extension_control_file(control, NULL);
- memset(values, 0, sizeof(values));
- memset(nulls, 0, sizeof(nulls));
+ memset(values, 0, sizeof(values));
+ memset(nulls, 0, sizeof(nulls));
- /* name */
- values[0] = DirectFunctionCall1(namein,
- CStringGetDatum(control->name));
- /* default_version */
- if (control->default_version == NULL)
- nulls[1] = true;
- else
- values[1] = CStringGetTextDatum(control->default_version);
- /* comment */
- if (control->comment == NULL)
- nulls[2] = true;
- else
- values[2] = CStringGetTextDatum(control->comment);
+ /* name */
+ values[0] = DirectFunctionCall1(namein,
+ CStringGetDatum(control->name));
+ /* default_version */
+ if (control->default_version == NULL)
+ nulls[1] = true;
+ else
+ values[1] = CStringGetTextDatum(control->default_version);
+ /* comment */
+ if (control->comment == NULL)
+ nulls[2] = true;
+ else
+ values[2] = CStringGetTextDatum(control->comment);
- tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
- values, nulls);
- }
+ tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
+ values, nulls);
+ }
- FreeDir(dir);
+ FreeDir(dir);
+ }
}
return (Datum) 0;
@@ -2201,51 +2291,57 @@ Datum
pg_available_extension_versions(PG_FUNCTION_ARGS)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
+ List *locations;
DIR *dir;
struct dirent *de;
/* Build tuplestore to hold the result rows */
InitMaterializedSRF(fcinfo, 0);
- location = get_extension_control_directory();
- dir = AllocateDir(location);
+ locations = get_extension_control_directories();
- /*
- * If the control directory doesn't exist, we want to silently return an
- * empty set. Any other error will be reported by ReadDir.
- */
- if (dir == NULL && errno == ENOENT)
- {
- /* do nothing */
- }
- else
+ foreach_ptr(char, location, locations)
{
- while ((de = ReadDir(dir, location)) != NULL)
+ dir = AllocateDir(location);
+
+ /*
+ * If the control directory doesn't exist, we want to silently return
+ * an empty set. Any other error will be reported by ReadDir.
+ */
+ if (dir == NULL && errno == ENOENT)
+ {
+ /* do nothing */
+ }
+ else
{
- ExtensionControlFile *control;
- char *extname;
+ while ((de = ReadDir(dir, location)) != NULL)
+ {
+ ExtensionControlFile *control;
+ char *extname;
- if (!is_extension_control_filename(de->d_name))
- continue;
+ if (!is_extension_control_filename(de->d_name))
+ continue;
- /* extract extension name from 'name.control' filename */
- extname = pstrdup(de->d_name);
- *strrchr(extname, '.') = '\0';
+ /* extract extension name from 'name.control' filename */
+ extname = pstrdup(de->d_name);
+ *strrchr(extname, '.') = '\0';
- /* ignore it if it's an auxiliary control file */
- if (strstr(extname, "--"))
- continue;
+ /* ignore it if it's an auxiliary control file */
+ if (strstr(extname, "--"))
+ continue;
- /* read the control file */
- control = read_extension_control_file(extname);
+ /* read the control file */
+ control = new_ExtensionControlFile(extname);
+ control->control_dir = pstrdup(location);
+ parse_extension_control_file(control, NULL);
- /* scan extension's script directory for install scripts */
- get_available_versions_for_extension(control, rsinfo->setResult,
- rsinfo->setDesc);
- }
+ /* scan extension's script directory for install scripts */
+ get_available_versions_for_extension(control, rsinfo->setResult,
+ rsinfo->setDesc);
+ }
- FreeDir(dir);
+ FreeDir(dir);
+ }
}
return (Datum) 0;
@@ -2373,47 +2469,53 @@ bool
extension_file_exists(const char *extensionName)
{
bool result = false;
- char *location;
+ List *locations;
DIR *dir;
struct dirent *de;
- location = get_extension_control_directory();
- dir = AllocateDir(location);
+ locations = get_extension_control_directories();
- /*
- * If the control directory doesn't exist, we want to silently return
- * false. Any other error will be reported by ReadDir.
- */
- if (dir == NULL && errno == ENOENT)
- {
- /* do nothing */
- }
- else
+ foreach_ptr(char, location, locations)
{
- while ((de = ReadDir(dir, location)) != NULL)
+ dir = AllocateDir(location);
+
+ /*
+ * If the control directory doesn't exist, we want to silently return
+ * false. Any other error will be reported by ReadDir.
+ */
+ if (dir == NULL && errno == ENOENT)
{
- char *extname;
+ /* do nothing */
+ }
+ else
+ {
+ while ((de = ReadDir(dir, location)) != NULL)
+ {
+ char *extname;
- if (!is_extension_control_filename(de->d_name))
- continue;
+ if (!is_extension_control_filename(de->d_name))
+ continue;
- /* extract extension name from 'name.control' filename */
- extname = pstrdup(de->d_name);
- *strrchr(extname, '.') = '\0';
+ /* extract extension name from 'name.control' filename */
+ extname = pstrdup(de->d_name);
+ *strrchr(extname, '.') = '\0';
- /* ignore it if it's an auxiliary control file */
- if (strstr(extname, "--"))
- continue;
+ /* ignore it if it's an auxiliary control file */
+ if (strstr(extname, "--"))
+ continue;
- /* done if it matches request */
- if (strcmp(extname, extensionName) == 0)
- {
- result = true;
- break;
+ /* done if it matches request */
+ if (strcmp(extname, extensionName) == 0)
+ {
+ result = true;
+ break;
+ }
}
- }
- FreeDir(dir);
+ FreeDir(dir);
+ }
+ if (result)
+ break;
}
return result;
@@ -3691,3 +3793,20 @@ read_whole_file(const char *filename, int *length)
*length = bytes_to_read;
return buf;
}
+
+static ExtensionControlFile *
+new_ExtensionControlFile(const char *extname)
+{
+ /*
+ * Set up default values. Pointer fields are initially null.
+ */
+ ExtensionControlFile *control = palloc0_object(ExtensionControlFile);
+
+ control->name = pstrdup(extname);
+ control->relocatable = false;
+ control->superuser = true;
+ control->trusted = false;
+ control->encoding = -1;
+
+ return control;
+}