mirror of
https://github.com/postgres/postgres.git
synced 2025-09-02 04:21:28 +03:00
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 <peter@eisentraut.org> Co-authored-by: Matheus Alcantara <matheusssilv97@gmail.com> Reviewed-by: David E. Wheeler <david@justatheory.com> Reviewed-by: Gabriele Bartolini <gabriele.bartolini@enterprisedb.com> Reviewed-by: Marco Nenciarini <marco.nenciarini@enterprisedb.com> Reviewed-by: Niccolò Fei <niccolo.fei@enterprisedb.com> Discussion: https://www.postgresql.org/message-id/flat/E7C7BFFB-8857-48D4-A71F-88B359FADCFD@justatheory.com
This commit is contained in:
@@ -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];
|
||||
|
||||
if (!is_extension_control_filename(de->d_name))
|
||||
continue;
|
||||
|
||||
/* 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;
|
||||
|
||||
control = read_extension_control_file(extname);
|
||||
|
||||
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);
|
||||
|
||||
tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
|
||||
values, nulls);
|
||||
/* do nothing */
|
||||
}
|
||||
else
|
||||
{
|
||||
while ((de = ReadDir(dir, location)) != NULL)
|
||||
{
|
||||
ExtensionControlFile *control;
|
||||
char *extname;
|
||||
Datum values[3];
|
||||
bool nulls[3];
|
||||
|
||||
FreeDir(dir);
|
||||
if (!is_extension_control_filename(de->d_name))
|
||||
continue;
|
||||
|
||||
/* 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;
|
||||
|
||||
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));
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
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)
|
||||
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;
|
||||
|
||||
if (!is_extension_control_filename(de->d_name))
|
||||
continue;
|
||||
|
||||
/* 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;
|
||||
|
||||
/* read the control file */
|
||||
control = read_extension_control_file(extname);
|
||||
|
||||
/* scan extension's script directory for install scripts */
|
||||
get_available_versions_for_extension(control, rsinfo->setResult,
|
||||
rsinfo->setDesc);
|
||||
/* do nothing */
|
||||
}
|
||||
else
|
||||
{
|
||||
while ((de = ReadDir(dir, location)) != NULL)
|
||||
{
|
||||
ExtensionControlFile *control;
|
||||
char *extname;
|
||||
|
||||
FreeDir(dir);
|
||||
if (!is_extension_control_filename(de->d_name))
|
||||
continue;
|
||||
|
||||
/* 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;
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
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)
|
||||
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
|
||||
* false. Any other error will be reported by ReadDir.
|
||||
*/
|
||||
if (dir == NULL && errno == ENOENT)
|
||||
{
|
||||
char *extname;
|
||||
|
||||
if (!is_extension_control_filename(de->d_name))
|
||||
continue;
|
||||
|
||||
/* 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;
|
||||
|
||||
/* done if it matches request */
|
||||
if (strcmp(extname, extensionName) == 0)
|
||||
{
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
/* do nothing */
|
||||
}
|
||||
else
|
||||
{
|
||||
while ((de = ReadDir(dir, location)) != NULL)
|
||||
{
|
||||
char *extname;
|
||||
|
||||
FreeDir(dir);
|
||||
if (!is_extension_control_filename(de->d_name))
|
||||
continue;
|
||||
|
||||
/* 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;
|
||||
|
||||
/* done if it matches request */
|
||||
if (strcmp(extname, extensionName) == 0)
|
||||
{
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
Reference in New Issue
Block a user