From: Andrew Dunstan Date: Thu, 1 Jan 2026 17:11:37 +0000 (-0500) Subject: Add paths of extensions to pg_available_extensions X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f3c9e341cdf167ae3378e74e770558e81f9aa48e;p=thirdparty%2Fpostgresql.git Add paths of extensions to pg_available_extensions Add a new "location" column to the pg_available_extensions and pg_available_extension_versions views, exposing the directory where the extension is located. The default system location is shown as '$system', the same value that can be used to configure the extension_control_path GUC. User-defined locations are only visible for super users, otherwise '' is returned as a column value, the same behaviour that we already use in pg_stat_activity. I failed to resist the temptation to do a little extra editorializing of the TAP test script. Catalog version bumped. Author: Matheus Alcantara Reviewed-By: Chao Li Reviewed-By: Rohit Prasad Reviewed-By: Michael Banck Reviewed-By: Manni Wood Reviewed-By: Euler Taveira Reviewed-By: Quan Zongliang --- diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml index 7ff7ca4f719..8b4abef8c68 100644 --- a/doc/src/sgml/system-views.sgml +++ b/doc/src/sgml/system-views.sgml @@ -599,6 +599,20 @@ + + + location text + + + The location where the extension is installed. If it is in the standard + system location, then the value will be $system, + while if it is found in the path specified by the + extension_control_path + GUC then the full path will be shown. + Only superusers can see this information. + + + comment text @@ -723,6 +737,20 @@ + + + location text + + + The location where the extension is installed. If it is in the standard + system location, then the value will be $system, + while if it is found in the path specified by the + extension_control_path + GUC then the full path will be shown. + Only superusers can see this information. + + + comment text diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 0a0f95f6bb9..a173779df32 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -412,14 +412,14 @@ CREATE VIEW pg_cursors AS 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; diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c index c43b74e319e..fc33e5569de 100644 --- a/src/backend/commands/extension.c +++ b/src/backend/commands/extension.c @@ -126,6 +126,21 @@ typedef struct 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, @@ -140,7 +155,8 @@ static Oid get_required_extension(char *reqExtensionName, 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, @@ -157,6 +173,29 @@ static ExtensionControlFile *new_ExtensionControlFile(const char *extname); 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 ""; + } +} + /* * get_extension_oid - given an extension name, look up the OID * @@ -354,7 +393,11 @@ get_extension_control_directories(void) 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 { @@ -366,6 +409,7 @@ get_extension_control_directories(void) 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) @@ -382,15 +426,21 @@ get_extension_control_directories(void) * 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') @@ -2215,9 +2265,9 @@ pg_available_extensions(PG_FUNCTION_ARGS) 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 @@ -2229,13 +2279,13 @@ pg_available_extensions(PG_FUNCTION_ARGS) } 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; @@ -2259,7 +2309,7 @@ pg_available_extensions(PG_FUNCTION_ARGS) 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)); @@ -2273,11 +2323,15 @@ pg_available_extensions(PG_FUNCTION_ARGS) 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); @@ -2313,9 +2367,9 @@ pg_available_extension_versions(PG_FUNCTION_ARGS) 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 @@ -2327,7 +2381,7 @@ pg_available_extension_versions(PG_FUNCTION_ARGS) } else { - while ((de = ReadDir(dir, location)) != NULL) + while ((de = ReadDir(dir, location->loc)) != NULL) { ExtensionControlFile *control; char *extname; @@ -2356,12 +2410,13 @@ pg_available_extension_versions(PG_FUNCTION_ARGS) /* 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); @@ -2378,7 +2433,8 @@ pg_available_extension_versions(PG_FUNCTION_ARGS) static void get_available_versions_for_extension(ExtensionControlFile *pcontrol, Tuplestorestate *tupstore, - TupleDesc tupdesc) + TupleDesc tupdesc, + ExtensionLocation *location) { List *evi_list; ListCell *lc; @@ -2391,8 +2447,8 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol, { ExtensionVersionInfo *evi = (ExtensionVersionInfo *) lfirst(lc); ExtensionControlFile *control; - Datum values[8]; - bool nulls[8]; + Datum values[9]; + bool nulls[9]; ListCell *lc2; if (!evi->installable) @@ -2428,11 +2484,15 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol, 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); @@ -2473,7 +2533,7 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol, 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); } @@ -3903,7 +3963,8 @@ find_in_paths(const char *basename, List *paths) foreach(cell, paths) { - char *path = lfirst(cell); + ExtensionLocation *location = lfirst(cell); + char *path = location->loc; char *full; Assert(path != NULL); diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 5f7886ab3ea..e25829fb05f 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -57,6 +57,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 202512301 +#define CATALOG_VERSION_NO 202601011 #endif diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 60f7ce502f6..722fffc67f0 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -10750,16 +10750,16 @@ { 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', diff --git a/src/test/modules/test_extensions/t/001_extension_control_path.pl b/src/test/modules/test_extensions/t/001_extension_control_path.pl index 7fbe5bde332..bc9e4c99c8e 100644 --- a/src/test/modules/test_extensions/t/001_extension_control_path.pl +++ b/src/test/modules/test_extensions/t/001_extension_control_path.pl @@ -25,6 +25,11 @@ my $ext_name2 = "test_custom_ext_paths_using_directory"; 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( @@ -35,6 +40,10 @@ extension_control_path = '\$system$sep@{[ $windows_os ? ($ext_dir =~ s/\\/\\\\/g # 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", @@ -46,48 +55,65 @@ $node->safe_psql('postgres', "CREATE EXTENSION $ext_name2"); 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, + "", + "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, + "", + "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 diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 4286c266e17..f4ee2bd7459 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1310,14 +1310,16 @@ pg_available_extension_versions| SELECT e.name, 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, diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index ceb3fc5d980..b9e671fcda8 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -800,6 +800,7 @@ ExtensibleNodeEntry ExtensibleNodeMethods ExtensionControlFile ExtensionInfo +ExtensionLocation ExtensionVersionInfo FDWCollateState FD_SET @@ -1583,7 +1584,6 @@ LoadStmt LocalBufferLookupEnt LocalPgBackendStatus LocalTransactionId -Location LocationIndex LocationLen LockAcquireResult