</para></entry>
</row>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>location</structfield> <type>text</type>
+ </para>
+ <para>
+ The location where the extension is installed. If it is in the standard
+ system location, then the value will be <literal>$system</literal>,
+ while if it is found in the path specified by the
+ <link linkend="guc-extension-control-path"><structname>extension_control_path</structname></link>
+ GUC then the full path will be shown.
+ Only superusers can see this information.
+ </para></entry>
+ </row>
+
<row>
<entry role="catalog_table_entry"><para role="column_definition">
<structfield>comment</structfield> <type>text</type>
</para></entry>
</row>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>location</structfield> <type>text</type>
+ </para>
+ <para>
+ The location where the extension is installed. If it is in the standard
+ system location, then the value will be <literal>$system</literal>,
+ while if it is found in the path specified by the
+ <link linkend="guc-extension-control-path"><structname>extension_control_path</structname></link>
+ GUC then the full path will be shown.
+ Only superusers can see this information.
+ </para></entry>
+ </row>
+
<row>
<entry role="catalog_table_entry"><para role="column_definition">
<structfield>comment</structfield> <type>text</type>
CREATE VIEW pg_available_extensions AS
SELECT E.name, E.default_version, X.extversion AS installed_version,
- E.comment
+ E.location, E.comment
FROM pg_available_extensions() AS E
LEFT JOIN pg_extension AS X ON E.name = X.extname;
CREATE VIEW pg_available_extension_versions AS
SELECT E.name, E.version, (X.extname IS NOT NULL) AS installed,
E.superuser, E.trusted, E.relocatable,
- E.schema, E.requires, E.comment
+ E.schema, E.requires, E.location, E.comment
FROM pg_available_extension_versions() AS E
LEFT JOIN pg_extension AS X
ON E.name = X.extname AND E.version = X.extversion;
ParseLoc stmt_len; /* length in bytes; 0 means "rest of string" */
} script_error_callback_arg;
+/*
+ * A location based on the extension_control_path GUC.
+ *
+ * The macro field stores the name of a macro (for example “$system”) that
+ * the extension_control_path processing supports, and which can be replaced
+ * by a system value stored in loc.
+ *
+ * For non-system paths the macro field is NULL.
+ */
+typedef struct
+{
+ char *macro;
+ char *loc;
+} ExtensionLocation;
+
/* Local functions */
static List *find_update_path(List *evi_list,
ExtensionVersionInfo *evi_start,
bool is_create);
static void get_available_versions_for_extension(ExtensionControlFile *pcontrol,
Tuplestorestate *tupstore,
- TupleDesc tupdesc);
+ TupleDesc tupdesc,
+ ExtensionLocation *location);
static Datum convert_requires_to_datum(List *requires);
static void ApplyExtensionUpdates(Oid extensionOid,
ExtensionControlFile *pcontrol,
char *find_in_paths(const char *basename, List *paths);
+/*
+ * Return the extension location. If the current user doesn't have sufficient
+ * privilege, don't show the location.
+ */
+static char *
+get_extension_location(ExtensionLocation *loc)
+{
+ /* We only want to show extension paths for superusers. */
+ if (superuser())
+ {
+ /* Return the macro value if present to avoid showing system paths. */
+ if (loc->macro != NULL)
+ return loc->macro;
+ else
+ return loc->loc;
+ }
+ else
+ {
+ /* Similar to pg_stat_activity for unprivileged users */
+ return "<insufficient privilege>";
+ }
+}
+
/*
* get_extension_oid - given an extension name, look up the OID
*
if (strlen(Extension_control_path) == 0)
{
- paths = lappend(paths, system_dir);
+ ExtensionLocation *location = palloc_object(ExtensionLocation);
+
+ location->macro = NULL;
+ location->loc = system_dir;
+ paths = lappend(paths, location);
}
else
{
int len;
char *mangled;
char *piece = first_path_var_separator(ecp);
+ ExtensionLocation *location = palloc_object(ExtensionLocation);
/* Get the length of the next path on ecp */
if (piece == NULL)
* suffix if it is a custom extension control path.
*/
if (strcmp(piece, "$system") == 0)
+ {
+ location->macro = pstrdup(piece);
mangled = substitute_path_macro(piece, "$system", system_dir);
+ }
else
+ {
+ location->macro = NULL;
mangled = psprintf("%s/extension", piece);
-
+ }
pfree(piece);
/* Canonicalize the path based on the OS and add to the list */
canonicalize_path(mangled);
- paths = lappend(paths, mangled);
+ location->loc = mangled;
+ paths = lappend(paths, location);
/* Break if ecp is empty or move to the next path on ecp */
if (ecp[len] == '\0')
locations = get_extension_control_directories();
- foreach_ptr(char, location, locations)
+ foreach_ptr(ExtensionLocation, location, locations)
{
- dir = AllocateDir(location);
+ dir = AllocateDir(location->loc);
/*
* If the control directory doesn't exist, we want to silently return
}
else
{
- while ((de = ReadDir(dir, location)) != NULL)
+ while ((de = ReadDir(dir, location->loc)) != NULL)
{
ExtensionControlFile *control;
char *extname;
String *extname_str;
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
if (!is_extension_control_filename(de->d_name))
continue;
found_ext = lappend(found_ext, extname_str);
control = new_ExtensionControlFile(extname);
- control->control_dir = pstrdup(location);
+ control->control_dir = pstrdup(location->loc);
parse_extension_control_file(control, NULL);
memset(values, 0, sizeof(values));
nulls[1] = true;
else
values[1] = CStringGetTextDatum(control->default_version);
+
+ /* location */
+ values[2] = CStringGetTextDatum(get_extension_location(location));
+
/* comment */
if (control->comment == NULL)
- nulls[2] = true;
+ nulls[3] = true;
else
- values[2] = CStringGetTextDatum(control->comment);
+ values[3] = CStringGetTextDatum(control->comment);
tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
values, nulls);
locations = get_extension_control_directories();
- foreach_ptr(char, location, locations)
+ foreach_ptr(ExtensionLocation, location, locations)
{
- dir = AllocateDir(location);
+ dir = AllocateDir(location->loc);
/*
* If the control directory doesn't exist, we want to silently return
}
else
{
- while ((de = ReadDir(dir, location)) != NULL)
+ while ((de = ReadDir(dir, location->loc)) != NULL)
{
ExtensionControlFile *control;
char *extname;
/* read the control file */
control = new_ExtensionControlFile(extname);
- control->control_dir = pstrdup(location);
+ control->control_dir = pstrdup(location->loc);
parse_extension_control_file(control, NULL);
/* scan extension's script directory for install scripts */
get_available_versions_for_extension(control, rsinfo->setResult,
- rsinfo->setDesc);
+ rsinfo->setDesc,
+ location);
}
FreeDir(dir);
static void
get_available_versions_for_extension(ExtensionControlFile *pcontrol,
Tuplestorestate *tupstore,
- TupleDesc tupdesc)
+ TupleDesc tupdesc,
+ ExtensionLocation *location)
{
List *evi_list;
ListCell *lc;
{
ExtensionVersionInfo *evi = (ExtensionVersionInfo *) lfirst(lc);
ExtensionControlFile *control;
- Datum values[8];
- bool nulls[8];
+ Datum values[9];
+ bool nulls[9];
ListCell *lc2;
if (!evi->installable)
nulls[6] = true;
else
values[6] = convert_requires_to_datum(control->requires);
+
+ /* location */
+ values[7] = CStringGetTextDatum(get_extension_location(location));
+
/* comment */
if (control->comment == NULL)
- nulls[7] = true;
+ nulls[8] = true;
else
- values[7] = CStringGetTextDatum(control->comment);
+ values[8] = CStringGetTextDatum(control->comment);
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
values[6] = convert_requires_to_datum(control->requires);
nulls[6] = false;
}
- /* comment stays the same */
+ /* comment and location stay the same */
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
}
foreach(cell, paths)
{
- char *path = lfirst(cell);
+ ExtensionLocation *location = lfirst(cell);
+ char *path = location->loc;
char *full;
Assert(path != NULL);
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 202512301
+#define CATALOG_VERSION_NO 202601011
#endif
{ oid => '3082', descr => 'list available extensions',
proname => 'pg_available_extensions', procost => '10', prorows => '100',
proretset => 't', provolatile => 's', prorettype => 'record',
- proargtypes => '', proallargtypes => '{name,text,text}',
- proargmodes => '{o,o,o}', proargnames => '{name,default_version,comment}',
+ proargtypes => '', proallargtypes => '{name,text,text,text}',
+ proargmodes => '{o,o,o,o}', proargnames => '{name,default_version,location,comment}',
prosrc => 'pg_available_extensions' },
{ oid => '3083', descr => 'list available extension versions',
proname => 'pg_available_extension_versions', procost => '10',
prorows => '100', proretset => 't', provolatile => 's',
prorettype => 'record', proargtypes => '',
- proallargtypes => '{name,text,bool,bool,bool,name,_name,text}',
- proargmodes => '{o,o,o,o,o,o,o,o}',
- proargnames => '{name,version,superuser,trusted,relocatable,schema,requires,comment}',
+ proallargtypes => '{name,text,bool,bool,bool,name,_name,text,text}',
+ proargmodes => '{o,o,o,o,o,o,o,o,o}',
+ proargnames => '{name,version,superuser,trusted,relocatable,schema,requires,location,comment}',
prosrc => 'pg_available_extension_versions' },
{ oid => '3084', descr => 'list an extension\'s version update paths',
proname => 'pg_extension_update_paths', procost => '10', prorows => '100',
mkpath("$ext_dir/$ext_name2");
create_extension($ext_name2, $ext_dir, $ext_name2);
+# Make windows path use Unix slashes as canonicalize_path() is called when
+# collecting extension control paths. See get_extension_control_directories().
+my $ext_dir_canonicalized = $ext_dir;
+$ext_dir_canonicalized =~ s!\\!/!g if $windows_os;
+
# Use the correct separator and escape \ when running on Windows.
my $sep = $windows_os ? ";" : ":";
$node->append_conf(
# Start node
$node->start;
+# Create an user to test permissions to read extension locations.
+my $user = "user01";
+$node->safe_psql('postgres', "CREATE USER $user");
+
my $ecp = $node->safe_psql('postgres', 'show extension_control_path;');
is($ecp, "\$system$sep$ext_dir$sep$ext_dir2",
my $ret = $node->safe_psql('postgres',
"select * from pg_available_extensions where name = '$ext_name'");
is( $ret,
- "test_custom_ext_paths|1.0|1.0|Test extension_control_path",
- "extension is installed correctly on pg_available_extensions");
+ "test_custom_ext_paths|1.0|1.0|$ext_dir_canonicalized/extension|Test extension_control_path",
+ "extension is shown correctly in pg_available_extensions");
$ret = $node->safe_psql('postgres',
"select * from pg_available_extension_versions where name = '$ext_name'");
is( $ret,
- "test_custom_ext_paths|1.0|t|t|f|t|||Test extension_control_path",
- "extension is installed correctly on pg_available_extension_versions");
+ "test_custom_ext_paths|1.0|t|t|f|t|||$ext_dir_canonicalized/extension|Test extension_control_path",
+ "extension is shown correctly in pg_available_extension_versions");
$ret = $node->safe_psql('postgres',
"select * from pg_available_extensions where name = '$ext_name2'");
is( $ret,
- "test_custom_ext_paths_using_directory|1.0|1.0|Test extension_control_path",
- "extension is installed correctly on pg_available_extensions");
+ "test_custom_ext_paths_using_directory|1.0|1.0|$ext_dir_canonicalized/extension|Test extension_control_path",
+ "extension is shown correctly in pg_available_extensions");
$ret = $node->safe_psql('postgres',
"select * from pg_available_extension_versions where name = '$ext_name2'"
);
is( $ret,
- "test_custom_ext_paths_using_directory|1.0|t|t|f|t|||Test extension_control_path",
- "extension is installed correctly on pg_available_extension_versions");
+ "test_custom_ext_paths_using_directory|1.0|t|t|f|t|||$ext_dir_canonicalized/extension|Test extension_control_path",
+ "extension is shown correctly in pg_available_extension_versions");
+
+# Test that a non-superuser is not able to read the extension location in
+# pg_available_extensions
+$ret = $node->safe_psql('postgres',
+ "select location from pg_available_extensions where name = '$ext_name2'",
+ connstr => "user=$user");
+is( $ret,
+ "<insufficient privilege>",
+ "extension location is hidden in pg_available_extensions for users with insufficient privilege");
-# Ensure that extensions installed on $system is still visible when using with
+# Test that a non-superuser is not able to read the extension location in
+# pg_available_extension_versions
+$ret = $node->safe_psql('postgres',
+ "select location from pg_available_extension_versions where name = '$ext_name2'",
+ connstr => "user=$user");
+is( $ret,
+ "<insufficient privilege>",
+ "extension location is hidden in pg_available_extension_versions for users with insufficient privilege");
+
+# Ensure that extensions installed in $system are still visible when used with
# custom extension control path.
$ret = $node->safe_psql('postgres',
"select count(*) > 0 as ok from pg_available_extensions where name = 'plpgsql'"
);
is($ret, "t",
- "\$system extension is installed correctly on pg_available_extensions");
-
+ "\$system extension is shown correctly in pg_available_extensions");
$ret = $node->safe_psql('postgres',
"set extension_control_path = ''; select count(*) > 0 as ok from pg_available_extensions where name = 'plpgsql'"
);
is($ret, "t",
- "\$system extension is installed correctly on pg_available_extensions with empty extension_control_path"
+ "\$system extension is shown correctly in pg_available_extensions with empty extension_control_path"
);
# Test with an extension that does not exists
my ($code, $stdout, $stderr) =
$node->psql('postgres', "CREATE EXTENSION invalid");
-is($code, 3, 'error to create an extension that does not exists');
+is($code, 3, 'error creating an extension that does not exist');
like($stderr, qr/ERROR: extension "invalid" is not available/);
sub create_extension
e.relocatable,
e.schema,
e.requires,
+ e.location,
e.comment
- FROM (pg_available_extension_versions() e(name, version, superuser, trusted, relocatable, schema, requires, comment)
+ FROM (pg_available_extension_versions() e(name, version, superuser, trusted, relocatable, schema, requires, location, comment)
LEFT JOIN pg_extension x ON (((e.name = x.extname) AND (e.version = x.extversion))));
pg_available_extensions| SELECT e.name,
e.default_version,
x.extversion AS installed_version,
+ e.location,
e.comment
- FROM (pg_available_extensions() e(name, default_version, comment)
+ FROM (pg_available_extensions() e(name, default_version, location, comment)
LEFT JOIN pg_extension x ON ((e.name = x.extname)));
pg_backend_memory_contexts| SELECT name,
ident,
ExtensibleNodeMethods
ExtensionControlFile
ExtensionInfo
+ExtensionLocation
ExtensionVersionInfo
FDWCollateState
FD_SET
LocalBufferLookupEnt
LocalPgBackendStatus
LocalTransactionId
-Location
LocationIndex
LocationLen
LockAcquireResult