Skip to content

Add extension_distdir GUC #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions doc/src/sgml/config.sgml
Original file line number Diff line number Diff line change
Expand Up @@ -10418,6 +10418,43 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
</listitem>
</varlistentry>

<varlistentry id="guc-extension-destdir" xreflabel="extension_destdir">
<term><varname>extension_destdir</varname> (<type>string</type>)
<indexterm>
<primary><varname>extension_destdir</varname> configuration parameter</primary>
</indexterm>
</term>
<listitem>
<para>
Specifies a directory prefix into which extensions should be
installed. Only superusers and users with the appropriate
<literal>SET</literal> privilege can change this setting. When set,
the postmaster will search this directory for an extension before
searching the default paths.
</para>

<para>
For example, this configuration:
<programlisting>
extension_destdir = '/mnt/extensions'
</programlisting>
will allow <productname>PostgreSQL</productname> to first look for
extension control files, SQL files, and loadable modules installed in
<literal>/mnt/extensions</literal> and fall back on the
default directories if they're not found there.
</para>

<para>
Note that the files should be installed in their full paths under the
<varname>extension_destdir</varname> prefix. When using
<link linkend="extend-pgxs">PGXS</link> to install an extension, pass
the destination directory via the <varname>DESTDIR</varname> variable
to install the files in the proper location. For more information see
<xref linkend="extend-extensions-files-directory"/>.
</para>
</listitem>
</varlistentry>

</variablelist>
</sect2>
</sect1>
Expand Down
12 changes: 11 additions & 1 deletion doc/src/sgml/extend.sgml
Original file line number Diff line number Diff line change
Expand Up @@ -669,7 +669,8 @@ RETURNS anycompatible AS ...
<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
the <literal>SHAREDIR</literal> under the <xref linkend="guc-extension-destdir"/>
prefix and to the installation's <literal>SHAREDIR</literal> directory. The
default behavior is equivalent to specifying
<literal>directory = 'extension'</literal>.
</para>
Expand Down Expand Up @@ -1710,6 +1711,15 @@ include $(PGXS)
</listitem>
</varlistentry>

<varlistentry id="extend-pgxs-destdir">
<term><varname>DESTDIR</varname></term>
<listitem>
<para>
install all files under this directory prefix
</para>
</listitem>
</varlistentry>

<varlistentry id="extend-pgxs-no-installcheck">
<term><varname>NO_INSTALLCHECK</varname></term>
<listitem>
Expand Down
90 changes: 90 additions & 0 deletions src/backend/commands/extension.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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);

Expand All @@ -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);
Expand Down Expand Up @@ -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)
Expand Down
29 changes: 28 additions & 1 deletion src/backend/utils/fmgr/dfmgr.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include "miscadmin.h"
#include "storage/fd.h"
#include "storage/shmem.h"
#include "utils/guc.h"
#include "utils/hsearch.h"


Expand Down Expand Up @@ -419,7 +420,7 @@ expand_dynamic_library_name(const char *name)
{
bool have_slash;
char *new;
char *full;
char *full, *full2;

Assert(name);

Expand All @@ -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);
Expand All @@ -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);
Expand Down
12 changes: 12 additions & 0 deletions src/backend/utils/misc/guc_tables.c
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,7 @@ char *ConfigFileName;
char *HbaFileName;
char *IdentFileName;
char *external_pid_file;
char *extension_destdir;

char *application_name;

Expand Down Expand Up @@ -4554,6 +4555,17 @@ struct config_string ConfigureNamesString[] =
check_canonical_path, NULL, NULL
},

{
{"extension_destdir", PGC_SUSET, FILE_LOCATIONS,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dynamic_library_path is CLIENT_CONN_OTHER not FILE_LOCATIONS. Should this be the same for consistency, and is there a security reason for the dynamic library path choice?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's superuser-only, so I'm not sure it matters. Happy to change it to whatever the consensus determines.

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,
Copy link

@ringerc ringerc Aug 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be made extensions_search_path or similar, like dynamic_library_path https://github.com/theory/postgres/blob/72e18d36734ced0cacb9c24022a85d88a14b4cb8/src/backend/utils/misc/guc_tables.c#L4161C39-L4161C56, so it can search multiple directories for extensions?

That would be a real help for container environments, local testing, etc, and would be more consistent with how the library search path works.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I replied to the -hackers thread, but I'd argue that's a completely different feature that requires a fair bit of thought. I would definitely prefer it, but the approach proposed here feels like a decent interim step to me, one that's already proven (in the Debian packaging system) and does most of what we need to solve the immediate challenges, at the expense of a slightly funky directory layout.

"",
NULL, NULL, NULL
},

{
{"ssl_library", PGC_INTERNAL, PRESET_OPTIONS,
gettext_noop("Shows the name of the SSL library."),
Expand Down
2 changes: 2 additions & 0 deletions src/backend/utils/misc/postgresql.conf.sample
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down
1 change: 1 addition & 0 deletions src/include/utils/guc.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down