From a69a8e54f3db4504f1c25913d706b1eeb0206138 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 8 Jul 2024 11:58:45 -0400 Subject: [PATCH] Add extension_destdir GUC Based on a [patch] by Christophe Berg in the Debian Project, add a new GUC, `extension_destdir`, that prepends a directory prefix for extension loading. This directory is prepended to the `SHAREDIR` paths when loading extensions (control and SQL files), and to the `$libdir` directive when loading modules that back functions. Requires a superuser or user with the appropriate SET privilege. Also document the PGXS `DESTDIR` variable, which should be used to install extensions into the proper destination directory. [patch]: https://salsa.debian.org/postgresql/postgresql/-/blob/17/debian/patches/extension_destdir?ref_type=heads --- doc/src/sgml/config.sgml | 37 ++++++++ doc/src/sgml/extend.sgml | 12 ++- src/backend/commands/extension.c | 90 +++++++++++++++++++ src/backend/utils/fmgr/dfmgr.c | 29 +++++- src/backend/utils/misc/guc_tables.c | 12 +++ src/backend/utils/misc/postgresql.conf.sample | 2 + src/include/utils/guc.h | 1 + 7 files changed, 181 insertions(+), 2 deletions(-) diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 0aec11f443212..b55b037e0f9e7 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -10418,6 +10418,43 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir' + + extension_destdir (string) + + extension_destdir configuration parameter + + + + + Specifies a directory prefix into which extensions should be + installed. Only superusers and users with the appropriate + SET privilege can change this setting. When set, + the postmaster will search this directory for an extension before + searching the default paths. + + + + For example, this configuration: + +extension_destdir = '/mnt/extensions' + + will allow PostgreSQL to first look for + extension control files, SQL files, and loadable modules installed in + /mnt/extensions and fall back on the + default directories if they're not found there. + + + + Note that the files should be installed in their full paths under the + extension_destdir prefix. When using + PGXS to install an extension, pass + the destination directory via the DESTDIR variable + to install the files in the proper location. For more information see + . + + + + diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml index 218940ee5ce19..6653955d53879 100644 --- a/doc/src/sgml/extend.sgml +++ b/doc/src/sgml/extend.sgml @@ -669,7 +669,8 @@ RETURNS anycompatible AS ... 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 + the SHAREDIR under the + prefix and to the installation's SHAREDIR directory. The default behavior is equivalent to specifying directory = 'extension'. @@ -1710,6 +1711,15 @@ include $(PGXS) + + DESTDIR + + + install all files under this directory prefix + + + + NO_INSTALLCHECK diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c index fab59ad5f6624..a084829b2c99e 100644 --- a/src/backend/commands/extension.c +++ b/src/backend/commands/extension.c @@ -337,6 +337,16 @@ get_extension_control_filename(const char *extname) get_share_path(my_exec_path, sharepath); result = (char *) palloc(MAXPGPATH); + /* + * If extension_destdir is set, try to find the file there first + */ + if (*extension_destdir != '\0') + { + snprintf(result, MAXPGPATH, "%s%s/extension/%s.control", + extension_destdir, sharepath, extname); + if (pg_file_exists(result)) + return result; + } snprintf(result, MAXPGPATH, "%s/extension/%s.control", sharepath, extname); @@ -376,6 +386,16 @@ get_extension_aux_control_filename(ExtensionControlFile *control, scriptdir = get_extension_script_directory(control); result = (char *) palloc(MAXPGPATH); + /* + * If extension_destdir is set, try to find the file there first + */ + if (*extension_destdir != '\0') + { + snprintf(result, MAXPGPATH, "%s%s/%s--%s.control", + extension_destdir, scriptdir, control->name, version); + if (pg_file_exists(result)) + return result; + } snprintf(result, MAXPGPATH, "%s/%s--%s.control", scriptdir, control->name, version); @@ -394,6 +414,23 @@ get_extension_script_filename(ExtensionControlFile *control, scriptdir = get_extension_script_directory(control); result = (char *) palloc(MAXPGPATH); + /* + * If extension_destdir is set, try to find the file there first + */ + if (*extension_destdir != '\0') + { + if (from_version) + snprintf(result, MAXPGPATH, "%s%s/%s--%s--%s.sql", + extension_destdir, scriptdir, control->name, from_version, version); + else + snprintf(result, MAXPGPATH, "%s%s/%s--%s.sql", + extension_destdir, scriptdir, control->name, version); + if (pg_file_exists(result)) + { + pfree(scriptdir); + return result; + } + } if (from_version) snprintf(result, MAXPGPATH, "%s/%s--%s--%s.sql", scriptdir, control->name, from_version, version); @@ -1153,6 +1190,59 @@ get_ext_ver_list(ExtensionControlFile *control) DIR *dir; struct dirent *de; + /* + * If extension_destdir is set, try to find the files there first + */ + if (*extension_destdir != '\0') + { + char location[MAXPGPATH]; + + snprintf(location, MAXPGPATH, "%s%s", extension_destdir, + get_extension_script_directory(control)); + dir = AllocateDir(location); + while ((de = ReadDir(dir, location)) != NULL) + { + char *vername; + char *vername2; + ExtensionVersionInfo *evi; + ExtensionVersionInfo *evi2; + + /* must be a .sql file ... */ + if (!is_extension_script_filename(de->d_name)) + continue; + + /* ... matching extension name followed by separator */ + if (strncmp(de->d_name, control->name, extnamelen) != 0 || + de->d_name[extnamelen] != '-' || + de->d_name[extnamelen + 1] != '-') + continue; + + /* extract version name(s) from 'extname--something.sql' filename */ + vername = pstrdup(de->d_name + extnamelen + 2); + *strrchr(vername, '.') = '\0'; + vername2 = strstr(vername, "--"); + if (!vername2) + { + /* It's an install, not update, script; record its version name */ + evi = get_ext_ver_info(vername, &evi_list); + evi->installable = true; + continue; + } + *vername2 = '\0'; /* terminate first version */ + vername2 += 2; /* and point to second */ + + /* if there's a third --, it's bogus, ignore it */ + if (strstr(vername2, "--")) + continue; + + /* Create ExtensionVersionInfos and link them together */ + evi = get_ext_ver_info(vername, &evi_list); + evi2 = get_ext_ver_info(vername2, &evi_list); + evi->reachable = lappend(evi->reachable, evi2); + } + FreeDir(dir); + } + location = get_extension_script_directory(control); dir = AllocateDir(location); while ((de = ReadDir(dir, location)) != NULL) diff --git a/src/backend/utils/fmgr/dfmgr.c b/src/backend/utils/fmgr/dfmgr.c index c7aa789b51b41..5c0cccfdae806 100644 --- a/src/backend/utils/fmgr/dfmgr.c +++ b/src/backend/utils/fmgr/dfmgr.c @@ -35,6 +35,7 @@ #include "miscadmin.h" #include "storage/fd.h" #include "storage/shmem.h" +#include "utils/guc.h" #include "utils/hsearch.h" @@ -419,7 +420,7 @@ expand_dynamic_library_name(const char *name) { bool have_slash; char *new; - char *full; + char *full, *full2; Assert(name); @@ -434,6 +435,19 @@ expand_dynamic_library_name(const char *name) else { full = substitute_libpath_macro(name); + /* + * If extension_destdir is set, try to find the file there first + */ + if (*extension_destdir != '\0') + { + full2 = psprintf("%s%s", extension_destdir, full); + if (pg_file_exists(full2)) + { + pfree(full); + return full2; + } + pfree(full2); + } if (pg_file_exists(full)) return full; pfree(full); @@ -452,6 +466,19 @@ expand_dynamic_library_name(const char *name) { full = substitute_libpath_macro(new); pfree(new); + /* + * If extension_destdir is set, try to find the file there first + */ + if (*extension_destdir != '\0') + { + full2 = psprintf("%s%s", extension_destdir, full); + if (pg_file_exists(full2)) + { + pfree(full); + return full2; + } + pfree(full2); + } if (pg_file_exists(full)) return full; pfree(full); diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c index 686309db58b98..d79741e140db7 100644 --- a/src/backend/utils/misc/guc_tables.c +++ b/src/backend/utils/misc/guc_tables.c @@ -539,6 +539,7 @@ char *ConfigFileName; char *HbaFileName; char *IdentFileName; char *external_pid_file; +char *extension_destdir; char *application_name; @@ -4554,6 +4555,17 @@ struct config_string ConfigureNamesString[] = check_canonical_path, NULL, NULL }, + { + {"extension_destdir", PGC_SUSET, FILE_LOCATIONS, + gettext_noop("Path to prepend for extension loading."), + gettext_noop("This directory is prepended to paths when loading extensions (control and SQL files), and to the '$libdir' directive when loading modules that back functions. The location is made configurable to allow build-time testing of extensions that do not have been installed to their proper location yet."), + GUC_SUPERUSER_ONLY + }, + &extension_destdir, + "", + NULL, NULL, NULL + }, + { {"ssl_library", PGC_INTERNAL, PRESET_OPTIONS, gettext_noop("Shows the name of the SSL library."), diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index 667e0dc40a24f..6a5164813521c 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -771,6 +771,8 @@ # - Other Defaults - #dynamic_library_path = '$libdir' +#extension_destdir = '' # prepend path when loading extensions + # and shared objects (added by Debian) #gin_fuzzy_search_limit = 0 diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h index 840b0fe57ff47..cfbd4b90eb3d4 100644 --- a/src/include/utils/guc.h +++ b/src/include/utils/guc.h @@ -285,6 +285,7 @@ extern PGDLLIMPORT char *ConfigFileName; extern PGDLLIMPORT char *HbaFileName; extern PGDLLIMPORT char *IdentFileName; extern PGDLLIMPORT char *external_pid_file; +extern PGDLLIMPORT char *extension_destdir; extern PGDLLIMPORT char *application_name;