From fdf1bd8abbb9aaff3747c57d653ef19f8be31fe8 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut 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 @@ Other Defaults + + extension_control_path (string) + + extension_control_path configuration parameter + + + + + A path to search for extensions, specifically extension control files + (name.control). The + remaining extension script and secondary control files are then loaded + from the same directory where the primary control file was found. + See for details. + + + + The value for extension_control_path 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 $system, the + compiled-in PostgreSQL extension + directory is substituted for $system; this + is where the extensions provided by the standard + PostgreSQL distribution are installed. + (Use pg_config --sharedir to find out the name of + this directory.) For example: + +extension_control_path = '/usr/local/share/postgresql/extension:/home/my_project/share/extension:$system' + + or, in a Windows environment: + +extension_control_path = 'C:\tools\postgresql\extension;H:\my_project\share\extension;$system' + + Note that the path elements should typically end in + extension if the normal installation layouts are + followed. (The value for $system already includes + the extension suffix.) + + + + The default value for this parameter is + '$system'. If the value is set to an empty + string, the default '$system' is also assumed. + + + + This parameter can be changed at run time by superusers and users + with the appropriate SET 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 postgresql.conf configuration + file. + + + + Note that if you set this parameter to be able to load extensions from + nonstandard locations, you will most likely also need to set to a correspondent location, for + example, + +extension_control_path = '/usr/local/share/postgresql/extension:$system' +dynamic_library_path = '/usr/local/lib/postgresql:$libdir' + + + + + gin_fuzzy_search_limit (integer) 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 @@ Extension Files control file can specify a different directory for the script file(s). + + Additional locations for extension control files can be configured using + the parameter . + + The file format for an extension control file is the same as for the postgresql.conf file, namely a list of @@ -669,9 +674,9 @@ Extension Files The directory containing the extension's SQL script file(s). Unless an absolute path is given, the name is relative to - the installation's SHAREDIR directory. The - default behavior is equivalent to specifying - directory = 'extension'. + the installation's SHAREDIR directory. By default, + the script files are looked for in the same directory where the + control file was found. @@ -719,8 +724,8 @@ Extension Files The value of this parameter will be substituted for each occurrence of MODULE_PATHNAME in the script file(s). If it is not - set, no substitution is made. Typically, this is set to - $libdir/shared_library_name and + set, no substitution is made. Typically, this is set to just + shared_library_name and then MODULE_PATHNAME is used in CREATE FUNCTION 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 @@ Extension Building Infrastructure setting PG_CONFIG to point to its pg_config program, either within the makefile or on the make command line. + You can also select a separate installation directory for your extension + by setting the make variable prefix + on the make command line. (But this will then require + additional setup to get the server to find the extension there.) 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 @@ Parameters The name of the extension to be installed. PostgreSQL will create the - extension using details from the file - SHAREDIR/extension/extension_name.control. + extension using details from the file extension_name.control, + found via the server's extension control path (set by .) 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