*** a/doc/src/sgml/config.sgml --- b/doc/src/sgml/config.sgml *************** *** 5773,5778 **** SET XML OPTION { DOCUMENT | CONTENT }; --- 5773,5827 ---- + + extension_control_path (string) + + extension_control_path configuration parameter + + extension packaging + + + The command CREATE EXTENSION searches for the extension + control file in order to install it. The value + for extension_control_path is used to search for + the name.control files. + + + + The value for extension_control_path must be a list + of absolute directory paths separated by colons (or semi-colons on + Windows). If a list element starts with the special + string $extdir, the + compiled-in PostgreSQL package extension + directory is substituted for $extdir; this is where + the extensions provided by the standard + PostgreSQL distribution are installed. + (Use pg_config --extdir to find out the name of + this directory.) For example: + + extension_control_path = '/usr/local/postgresql/extension:/home/my_project:$extdir' + + or, in a Windows environment: + + extension_control_path = 'C:\tools\postgresql;H:\my_project\lib;$extdir' + + + + + The default value for this parameter is '$extdir'. + + + + This parameter can be changed at run time by superusers, but a + setting done that way will only persist until the end of the + client connection, so this method should be reserved for + development purposes. The recommended way to set this parameter + is in the postgresql.conf configuration + file. + + + + dynamic_library_path (string) *** a/src/backend/commands/extension.c --- b/src/backend/commands/extension.c *************** *** 25,30 **** --- 25,31 ---- #include #include + #include #include #include "access/htup_details.h" *************** *** 60,71 **** --- 61,76 ---- bool creating_extension = false; Oid CurrentExtensionObject = InvalidOid; + /* GUC extension_control_path */ + char *Extension_control_path; + /* * Internal data structure to hold the results of parsing a control file */ typedef struct ExtensionControlFile { char *name; /* name of the extension */ + char *filename; /* full path of the extension control file */ char *directory; /* directory for script files */ char *default_version; /* default install target version, if any */ char *module_pathname; /* string to substitute for MODULE_PATHNAME */ *************** *** 342,393 **** is_extension_script_filename(const char *filename) return (extension != NULL) && (strcmp(extension, ".sql") == 0); } static char * ! get_extension_control_directory(void) { char sharepath[MAXPGPATH]; ! char *result; get_share_path(my_exec_path, sharepath); ! result = (char *) palloc(MAXPGPATH); ! snprintf(result, MAXPGPATH, "%s/extension", sharepath); ! return result; } static char * ! get_extension_control_filename(const char *extname) { ! char sharepath[MAXPGPATH]; ! char *result; ! get_share_path(my_exec_path, sharepath); ! result = (char *) palloc(MAXPGPATH); ! snprintf(result, MAXPGPATH, "%s/extension/%s.control", ! sharepath, extname); ! return result; } static char * get_extension_script_directory(ExtensionControlFile *control) { ! char sharepath[MAXPGPATH]; char *result; /* * The directory parameter can be omitted, absolute, or relative to the ! * installation's share directory. */ if (!control->directory) ! return get_extension_control_directory(); if (is_absolute_path(control->directory)) return pstrdup(control->directory); ! get_share_path(my_exec_path, sharepath); result = (char *) palloc(MAXPGPATH); ! snprintf(result, MAXPGPATH, "%s/%s", sharepath, control->directory); return result; } --- 347,572 ---- return (extension != NULL) && (strcmp(extension, ".sql") == 0); } + /* + * Substitute for any macros appearing in the given string. + * Result is always freshly palloc'd. + */ static char * ! substitute_extension_control_path_macro(const char *name) { char sharepath[MAXPGPATH]; ! const char *sep_ptr; ! ! AssertArg(name != NULL); ! ! /* Currently, we only recognize $extdir at the start of the string */ ! if (name[0] != '$') ! return pstrdup(name); ! ! if ((sep_ptr = first_dir_separator(name)) == NULL) ! sep_ptr = name + strlen(name); ! ! if (strlen("$extdir") != sep_ptr - name || ! strncmp(name, "$extdir", strlen("$extdir")) != 0) ! ereport(ERROR, ! (errcode(ERRCODE_INVALID_NAME), ! errmsg("invalid macro name in extension control path: %s", ! name))); get_share_path(my_exec_path, sharepath); ! return psprintf("%s/extension%s", sharepath, sep_ptr); ! } ! /* ! * XXX: move that function to somewhere both src/backend/utils/fmgr/dfmgr.c ! * and this file can use it. ! */ ! static bool ! file_exists(const char *name) ! { ! struct stat st; ! ! AssertArg(name != NULL); ! ! if (stat(name, &st) == 0) ! return S_ISDIR(st.st_mode) ? false : true; ! else if (!(errno == ENOENT || errno == ENOTDIR || errno == EACCES)) ! ereport(ERROR, ! (errcode_for_file_access(), ! errmsg("could not access file \"%s\": %m", name))); ! ! return false; } + /* + * Search for a file called 'basename' in the colon-separated search + * path Extension_control_path. If the file is found, the full file name + * is returned in freshly palloc'd memory. If the file is not found, + * return NULL. + */ static char * ! find_in_extension_control_path(const char *basename) { ! const char *p; ! size_t baselen; ! AssertArg(basename != NULL); ! AssertArg(first_dir_separator(basename) == NULL); ! AssertState(Extension_control_path != NULL); ! p = Extension_control_path; ! if (strlen(p) == 0) ! return NULL; ! ! baselen = strlen(basename); ! ! for (;;) ! { ! size_t len; ! char *piece; ! char *mangled; ! char *full; ! ! piece = first_path_var_separator(p); ! if (piece == p) ! ereport(ERROR, ! (errcode(ERRCODE_INVALID_NAME), ! errmsg("zero-length component in parameter \"extension_control__path\""))); ! ! if (piece == NULL) ! len = strlen(p); ! else ! len = piece - p; ! ! piece = palloc(len + 1); ! strlcpy(piece, p, len + 1); ! ! mangled = substitute_extension_control_path_macro(piece); ! pfree(piece); ! ! canonicalize_path(mangled); ! ! /* only absolute paths */ ! if (!is_absolute_path(mangled)) ! ereport(ERROR, ! (errcode(ERRCODE_INVALID_NAME), ! errmsg("component in parameter \"extension_control_path\" is not an absolute path"))); ! ! /* don't forget to count for ".control", 8 chars */ ! full = palloc(strlen(mangled) + 1 + baselen + 8 + 1); ! sprintf(full, "%s/%s.control", mangled, basename); ! pfree(mangled); ! ! elog(DEBUG3, "find_in_extension_control_path: trying \"%s\"", full); ! ! if (file_exists(full)) ! return full; ! ! pfree(full); ! ! if (p[len] == '\0') ! break; ! else ! p += len + 1; ! } ! ! return NULL; ! } ! ! /* ! * Return the current list of extension_control_path directories, with $extdir ! * macro expanded. ! */ ! static List * ! list_extension_control_paths() ! { ! List *paths = NIL; ! const char *p; ! ! AssertState(Extension_control_path != NULL); ! ! p = Extension_control_path; ! if (strlen(p) == 0) ! return NULL; ! ! for (;;) ! { ! size_t len; ! char *piece; ! char *mangled; ! ! piece = first_path_var_separator(p); ! if (piece == p) ! ereport(ERROR, ! (errcode(ERRCODE_INVALID_NAME), ! errmsg("zero-length component in parameter \"extension_control__path\""))); ! ! if (piece == NULL) ! len = strlen(p); ! else ! len = piece - p; ! ! piece = palloc(len + 1); ! strlcpy(piece, p, len + 1); ! ! mangled = substitute_extension_control_path_macro(piece); ! pfree(piece); ! ! canonicalize_path(mangled); ! ! /* only absolute paths */ ! if (!is_absolute_path(mangled)) ! ereport(ERROR, ! (errcode(ERRCODE_INVALID_NAME), ! errmsg("component in parameter \"extension_control_path\" is not an absolute path"))); ! ! paths = lappend(paths, mangled); ! ! if (p[len] == '\0') ! break; ! else ! p += len + 1; ! } ! ! return paths; ! } ! ! static char * ! get_extension_control_directory(ExtensionControlFile *control) ! { ! char *filename = pstrdup(control->filename); ! ! get_parent_directory(filename); ! ! return filename; ! } ! ! static char * ! get_extension_control_filename(const char *extname) ! { ! return find_in_extension_control_path(extname); } static char * get_extension_script_directory(ExtensionControlFile *control) { ! char *directory; char *result; /* * The directory parameter can be omitted, absolute, or relative to the ! * extension's main control file's parent directory. */ if (!control->directory) ! return get_extension_control_directory(control); if (is_absolute_path(control->directory)) return pstrdup(control->directory); ! /* control->directory is relative to control->filename parent's directory */ ! directory = get_extension_control_directory(control); result = (char *) palloc(MAXPGPATH); ! snprintf(result, MAXPGPATH, "%s/%s", directory, control->directory); return result; } *************** *** 482,487 **** parse_extension_control_file(ExtensionControlFile *control, --- 661,668 ---- FreeFile(file); + control->filename = pstrdup(filename); + /* * Convert the ConfigVariable list into ExtensionControlFile entries. */ *************** *** 1623,1675 **** RemoveExtensionById(Oid extId) } /* ! * This function lists the available extensions (one row per primary control ! * file in the control directory). We parse each control file and report the ! * interesting fields. ! * ! * The system view pg_available_extensions provides a user interface to this ! * SRF, adding information about whether the extensions are installed in the ! * current DB. */ ! Datum ! pg_available_extensions(PG_FUNCTION_ARGS) { - ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; - TupleDesc tupdesc; - Tuplestorestate *tupstore; - MemoryContext per_query_ctx; - MemoryContext oldcontext; - char *location; DIR *dir; struct dirent *de; - /* check to see if caller supports us returning a tuplestore */ - if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("set-valued function called in context that cannot accept a set"))); - if (!(rsinfo->allowedModes & SFRM_Materialize)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("materialize mode required, but it is not " \ - "allowed in this context"))); - - /* Build a tuple descriptor for our result type */ - if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) - elog(ERROR, "return type must be a row type"); - - /* Build tuplestore to hold the result rows */ - per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; - oldcontext = MemoryContextSwitchTo(per_query_ctx); - - tupstore = tuplestore_begin_heap(true, false, work_mem); - rsinfo->returnMode = SFRM_Materialize; - rsinfo->setResult = tupstore; - rsinfo->setDesc = tupdesc; - - MemoryContextSwitchTo(oldcontext); - - location = get_extension_control_directory(); dir = AllocateDir(location); /* --- 1804,1819 ---- } /* ! * Add available extensions informations (from the control files found in ! * location) to the pg_available_extension tuple store. */ ! static void ! list_available_extensions(TupleDesc tupdesc, Tuplestorestate *tupstore, ! const char *location) { DIR *dir; struct dirent *de; dir = AllocateDir(location); /* *************** *** 1721,1729 **** pg_available_extensions(PG_FUNCTION_ARGS) tuplestore_putvalues(tupstore, tupdesc, values, nulls); } - FreeDir(dir); } /* clean up and return the tuplestore */ tuplestore_donestoring(tupstore); --- 1865,1926 ---- tuplestore_putvalues(tupstore, tupdesc, values, nulls); } FreeDir(dir); } + } + + /* + * This function lists the available extensions (one row per primary control + * file in the control directory). We parse each control file and report the + * interesting fields. + * + * The system view pg_available_extensions provides a user interface to this + * SRF, adding information about whether the extensions are installed in the + * current DB. + */ + Datum + pg_available_extensions(PG_FUNCTION_ARGS) + { + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + TupleDesc tupdesc; + Tuplestorestate *tupstore; + MemoryContext per_query_ctx; + MemoryContext oldcontext; + List *extension_control_paths = list_extension_control_paths(); + ListCell *lc; + + /* check to see if caller supports us returning a tuplestore */ + if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + if (!(rsinfo->allowedModes & SFRM_Materialize)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialize mode required, but it is not " \ + "allowed in this context"))); + + /* Build a tuple descriptor for our result type */ + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + /* Build tuplestore to hold the result rows */ + per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; + oldcontext = MemoryContextSwitchTo(per_query_ctx); + + tupstore = tuplestore_begin_heap(true, false, work_mem); + rsinfo->returnMode = SFRM_Materialize; + rsinfo->setResult = tupstore; + rsinfo->setDesc = tupdesc; + + MemoryContextSwitchTo(oldcontext); + + foreach(lc, extension_control_paths) + { + char *location = (char *) lfirst(lc); + + list_available_extensions(tupdesc, tupstore, location); + } /* clean up and return the tuplestore */ tuplestore_donestoring(tupstore); *************** *** 1748,1754 **** pg_available_extension_versions(PG_FUNCTION_ARGS) Tuplestorestate *tupstore; MemoryContext per_query_ctx; MemoryContext oldcontext; ! char *location; DIR *dir; struct dirent *de; --- 1945,1952 ---- Tuplestorestate *tupstore; MemoryContext per_query_ctx; MemoryContext oldcontext; ! List *extension_control_paths = list_extension_control_paths(); ! ListCell *lc; DIR *dir; struct dirent *de; *************** *** 1778,1820 **** pg_available_extension_versions(PG_FUNCTION_ARGS) MemoryContextSwitchTo(oldcontext); ! location = get_extension_control_directory(); ! 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) { ! /* do nothing */ ! } ! else ! { ! while ((de = ReadDir(dir, location)) != NULL) { ! 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, tupstore, tupdesc); ! } ! FreeDir(dir); } /* clean up and return the tuplestore */ --- 1976,2022 ---- MemoryContextSwitchTo(oldcontext); ! foreach(lc, extension_control_paths) { ! char *location = (char *) lfirst(lc); ! ! 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) { ! /* do nothing */ ! } ! else ! { ! while ((de = ReadDir(dir, location)) != NULL) ! { ! 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, tupstore, tupdesc); ! } ! FreeDir(dir); ! } } /* clean up and return the tuplestore */ *** a/src/backend/utils/misc/guc.c --- b/src/backend/utils/misc/guc.c *************** *** 32,37 **** --- 32,38 ---- #include "access/xact.h" #include "catalog/namespace.h" #include "commands/async.h" + #include "commands/extension.h" #include "commands/prepare.h" #include "commands/vacuum.h" #include "commands/variable.h" *************** *** 2726,2731 **** static struct config_string ConfigureNamesString[] = --- 2727,2745 ---- }, { + {"extension_control_path", PGC_SUSET, CLIENT_CONN_OTHER, + gettext_noop("Sets the path for extension control files."), + gettext_noop("If an extension control file needs to be opened " + "the system will search this path for " + "the specified file."), + GUC_SUPERUSER_ONLY + }, + &Extension_control_path, + "$extdir", + NULL, NULL, NULL + }, + + { {"krb_server_keyfile", PGC_SIGHUP, CONN_AUTH_SECURITY, gettext_noop("Sets the location of the Kerberos server key file."), NULL, *** a/src/backend/utils/misc/postgresql.conf.sample --- b/src/backend/utils/misc/postgresql.conf.sample *************** *** 537,542 **** --- 537,543 ---- # - Other Defaults - #dynamic_library_path = '$libdir' + #extension_control_path = '$extdir' #local_preload_libraries = '' #session_preload_libraries = '' *** a/src/bin/pg_config/pg_config.c --- b/src/bin/pg_config/pg_config.c *************** *** 146,151 **** show_includedir_server(bool all) --- 146,163 ---- } static void + show_extdir(bool all) + { + char path[MAXPGPATH]; + + if (all) + printf("EXTDIR = "); + get_share_path(mypath, path); + cleanup_path(path); + printf("%s/extension\n", path); + } + + static void show_libdir(bool all) { char path[MAXPGPATH]; *************** *** 402,407 **** static const InfoItem info_items[] = { --- 414,420 ---- {"--pkgincludedir", show_pkgincludedir}, {"--includedir-server", show_includedir_server}, {"--libdir", show_libdir}, + {"--extdir", show_extdir}, {"--pkglibdir", show_pkglibdir}, {"--localedir", show_localedir}, {"--mandir", show_mandir}, *************** *** 436,441 **** help(void) --- 449,455 ---- " interfaces\n")); printf(_(" --pkgincludedir show location of other C header files\n")); printf(_(" --includedir-server show location of C header files for the server\n")); + printf(_(" --extdir show location of extension control files\n")); printf(_(" --libdir show location of object code libraries\n")); printf(_(" --pkglibdir show location of dynamically loadable modules\n")); printf(_(" --localedir show location of locale support files\n")); *** a/src/include/commands/extension.h --- b/src/include/commands/extension.h *************** *** 26,31 **** --- 26,33 ---- extern bool creating_extension; extern Oid CurrentExtensionObject; + /* GUC extension_control_path */ + extern char *Extension_control_path; extern Oid CreateExtension(CreateExtensionStmt *stmt);