X-Git-Url: https://granicus.if.org/sourcecode?a=blobdiff_plain;f=src%2Fbackend%2Futils%2Finit%2Fmiscinit.c;h=0f734260c16d71136055344b3195c070d458d0e1;hb=efae4653c98fd201a8a723bceabf182a1005ac0f;hp=599fc9938b609fcfa9dfe6a42f5216cadfc04899;hpb=2abae34a2e8fde42be731b4e18d44cd08901464d;p=postgresql diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index 599fc9938b..0f734260c1 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -3,12 +3,12 @@ * miscinit.c * miscellaneous initialization support stuff * - * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/init/miscinit.c,v 1.163 2007/09/03 00:39:18 tgl Exp $ + * src/backend/utils/init/miscinit.c * *------------------------------------------------------------------------- */ @@ -30,8 +30,10 @@ #endif #include "catalog/pg_authid.h" +#include "mb/pg_wchar.h" #include "miscadmin.h" #include "postmaster/autovacuum.h" +#include "postmaster/postmaster.h" #include "storage/fd.h" #include "storage/ipc.h" #include "storage/pg_shmem.h" @@ -39,6 +41,7 @@ #include "storage/procarray.h" #include "utils/builtins.h" #include "utils/guc.h" +#include "utils/memutils.h" #include "utils/syscache.h" @@ -62,62 +65,6 @@ static char socketLockFile[MAXPGPATH]; bool IgnoreSystemIndexes = false; -/* ---------------------------------------------------------------- - * system index reindexing support - * - * When we are busy reindexing a system index, this code provides support - * for preventing catalog lookups from using that index. - * ---------------------------------------------------------------- - */ - -static Oid currentlyReindexedHeap = InvalidOid; -static Oid currentlyReindexedIndex = InvalidOid; - -/* - * ReindexIsProcessingHeap - * True if heap specified by OID is currently being reindexed. - */ -bool -ReindexIsProcessingHeap(Oid heapOid) -{ - return heapOid == currentlyReindexedHeap; -} - -/* - * ReindexIsProcessingIndex - * True if index specified by OID is currently being reindexed. - */ -bool -ReindexIsProcessingIndex(Oid indexOid) -{ - return indexOid == currentlyReindexedIndex; -} - -/* - * SetReindexProcessing - * Set flag that specified heap/index are being reindexed. - */ -void -SetReindexProcessing(Oid heapOid, Oid indexOid) -{ - Assert(OidIsValid(heapOid) && OidIsValid(indexOid)); - /* Reindexing is not re-entrant. */ - if (OidIsValid(currentlyReindexedIndex)) - elog(ERROR, "cannot reindex while reindexing"); - currentlyReindexedHeap = heapOid; - currentlyReindexedIndex = indexOid; -} - -/* - * ResetReindexProcessing - * Unset reindexing status. - */ -void -ResetReindexProcessing(void) -{ - currentlyReindexedHeap = InvalidOid; - currentlyReindexedIndex = InvalidOid; -} /* ---------------------------------------------------------------- * database path / name support stuff @@ -127,17 +74,9 @@ ResetReindexProcessing(void) void SetDatabasePath(const char *path) { - if (DatabasePath) - { - free(DatabasePath); - DatabasePath = NULL; - } - /* use strdup since this is done before memory contexts are set up */ - if (path) - { - DatabasePath = strdup(path); - AssertState(DatabasePath); - } + /* This should happen only once per process */ + Assert(!DatabasePath); + DatabasePath = MemoryContextStrdup(TopMemoryContext, path); } /* @@ -264,13 +203,17 @@ make_absolute_path(const char *path) * OuterUserId is the current user ID in effect at the "outer level" (outside * any transaction or function). This is initially the same as SessionUserId, * but can be changed by SET ROLE to any role that SessionUserId is a - * member of. We store this mainly so that AtAbort_UserId knows what to - * reset CurrentUserId to. + * member of. (XXX rename to something like CurrentRoleId?) * * CurrentUserId is the current effective user ID; this is the one to use * for all normal permissions-checking purposes. At outer level this will * be the same as OuterUserId, but it changes during calls to SECURITY * DEFINER functions, as well as locally in some specialized commands. + * + * SecurityRestrictionContext holds flags indicating reason(s) for changing + * CurrentUserId. In some cases we need to lock down operations that are + * not directly controlled by privilege settings, and this provides a + * convenient way to do it. * ---------------------------------------------------------------- */ static Oid AuthenticatedUserId = InvalidOid; @@ -282,12 +225,16 @@ static Oid CurrentUserId = InvalidOid; static bool AuthenticatedUserIsSuperuser = false; static bool SessionUserIsSuperuser = false; +static int SecurityRestrictionContext = 0; + /* We also remember if a SET ROLE is currently active */ static bool SetRoleIsActive = false; /* - * GetUserId/SetUserId - get/set the current effective user ID. + * GetUserId - get the current effective user ID. + * + * Note: there's no SetUserId() anymore; use SetUserIdAndSecContext(). */ Oid GetUserId(void) @@ -297,14 +244,6 @@ GetUserId(void) } -void -SetUserId(Oid userid) -{ - AssertArg(OidIsValid(userid)); - CurrentUserId = userid; -} - - /* * GetOuterUserId/SetOuterUserId - get/set the outer-level user ID. */ @@ -319,6 +258,7 @@ GetOuterUserId(void) static void SetOuterUserId(Oid userid) { + AssertState(SecurityRestrictionContext == 0); AssertArg(OidIsValid(userid)); OuterUserId = userid; @@ -341,6 +281,7 @@ GetSessionUserId(void) static void SetSessionUserId(Oid userid, bool is_superuser) { + AssertState(SecurityRestrictionContext == 0); AssertArg(OidIsValid(userid)); SessionUserId = userid; SessionUserIsSuperuser = is_superuser; @@ -352,6 +293,119 @@ SetSessionUserId(Oid userid, bool is_superuser) } +/* + * GetUserIdAndSecContext/SetUserIdAndSecContext - get/set the current user ID + * and the SecurityRestrictionContext flags. + * + * Currently there are two valid bits in SecurityRestrictionContext: + * + * SECURITY_LOCAL_USERID_CHANGE indicates that we are inside an operation + * that is temporarily changing CurrentUserId via these functions. This is + * needed to indicate that the actual value of CurrentUserId is not in sync + * with guc.c's internal state, so SET ROLE has to be disallowed. + * + * SECURITY_RESTRICTED_OPERATION indicates that we are inside an operation + * that does not wish to trust called user-defined functions at all. This + * bit prevents not only SET ROLE, but various other changes of session state + * that normally is unprotected but might possibly be used to subvert the + * calling session later. An example is replacing an existing prepared + * statement with new code, which will then be executed with the outer + * session's permissions when the prepared statement is next used. Since + * these restrictions are fairly draconian, we apply them only in contexts + * where the called functions are really supposed to be side-effect-free + * anyway, such as VACUUM/ANALYZE/REINDEX. + * + * Unlike GetUserId, GetUserIdAndSecContext does *not* Assert that the current + * value of CurrentUserId is valid; nor does SetUserIdAndSecContext require + * the new value to be valid. In fact, these routines had better not + * ever throw any kind of error. This is because they are used by + * StartTransaction and AbortTransaction to save/restore the settings, + * and during the first transaction within a backend, the value to be saved + * and perhaps restored is indeed invalid. We have to be able to get + * through AbortTransaction without asserting in case InitPostgres fails. + */ +void +GetUserIdAndSecContext(Oid *userid, int *sec_context) +{ + *userid = CurrentUserId; + *sec_context = SecurityRestrictionContext; +} + +void +SetUserIdAndSecContext(Oid userid, int sec_context) +{ + CurrentUserId = userid; + SecurityRestrictionContext = sec_context; +} + + +/* + * InLocalUserIdChange - are we inside a local change of CurrentUserId? + */ +bool +InLocalUserIdChange(void) +{ + return (SecurityRestrictionContext & SECURITY_LOCAL_USERID_CHANGE) != 0; +} + +/* + * InSecurityRestrictedOperation - are we inside a security-restricted command? + */ +bool +InSecurityRestrictedOperation(void) +{ + return (SecurityRestrictionContext & SECURITY_RESTRICTED_OPERATION) != 0; +} + + +/* + * These are obsolete versions of Get/SetUserIdAndSecContext that are + * only provided for bug-compatibility with some rather dubious code in + * pljava. We allow the userid to be set, but only when not inside a + * security restriction context. + */ +void +GetUserIdAndContext(Oid *userid, bool *sec_def_context) +{ + *userid = CurrentUserId; + *sec_def_context = InLocalUserIdChange(); +} + +void +SetUserIdAndContext(Oid userid, bool sec_def_context) +{ + /* We throw the same error SET ROLE would. */ + if (InSecurityRestrictedOperation()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("cannot set parameter \"%s\" within security-restricted operation", + "role"))); + CurrentUserId = userid; + if (sec_def_context) + SecurityRestrictionContext |= SECURITY_LOCAL_USERID_CHANGE; + else + SecurityRestrictionContext &= ~SECURITY_LOCAL_USERID_CHANGE; +} + + +/* + * Check if the authenticated user is a replication role + */ +bool +is_authenticated_user_replication_role(void) +{ + bool result = false; + HeapTuple utup; + + utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(AuthenticatedUserId)); + if (HeapTupleIsValid(utup)) + { + result = ((Form_pg_authid) GETSTRUCT(utup))->rolreplication; + ReleaseSysCache(utup); + } + return result; +} + /* * Initialize user identity during normal backend startup */ @@ -360,8 +414,6 @@ InitializeSessionUserId(const char *rolename) { HeapTuple roleTup; Form_pg_authid rform; - Datum datum; - bool isnull; Oid roleid; /* @@ -373,9 +425,7 @@ InitializeSessionUserId(const char *rolename) /* call only once */ AssertState(!OidIsValid(AuthenticatedUserId)); - roleTup = SearchSysCache(AUTHNAME, - PointerGetDatum(rolename), - 0, 0, 0); + roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(rolename)); if (!HeapTupleIsValid(roleTup)) ereport(FATAL, (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), @@ -398,10 +448,8 @@ InitializeSessionUserId(const char *rolename) * These next checks are not enforced when in standalone mode, so that * there is a way to recover from sillinesses like "UPDATE pg_authid SET * rolcanlogin = false;". - * - * We do not enforce them for the autovacuum process either. */ - if (IsUnderPostmaster && !IsAutoVacuumWorkerProcess()) + if (IsUnderPostmaster) { /* * Is role allowed to login at all? @@ -438,24 +486,6 @@ InitializeSessionUserId(const char *rolename) AuthenticatedUserIsSuperuser ? "on" : "off", PGC_INTERNAL, PGC_S_OVERRIDE); - /* - * Set up user-specific configuration variables. This is a good place to - * do it so we don't have to read pg_authid twice during session startup. - */ - datum = SysCacheGetAttr(AUTHNAME, roleTup, - Anum_pg_authid_rolconfig, &isnull); - if (!isnull) - { - ArrayType *a = DatumGetArrayTypeP(datum); - - /* - * We process all the options at SUSET level. We assume that the - * right to insert an option into pg_authid was checked when it was - * inserted. - */ - ProcessGUCArray(a, PGC_SUSET, PGC_S_USER, false); - } - ReleaseSysCache(roleTup); } @@ -466,7 +496,10 @@ InitializeSessionUserId(const char *rolename) void InitializeSessionUserIdStandalone(void) { - /* This function should only be called in a single-user backend. */ + /* + * This function should only be called in single-user mode and in + * autovacuum workers. + */ AssertState(!IsUnderPostmaster || IsAutoVacuumWorkerProcess()); /* call only once */ @@ -479,21 +512,6 @@ InitializeSessionUserIdStandalone(void) } -/* - * Reset effective userid during AbortTransaction - * - * This is essentially SetUserId(GetOuterUserId()), but without the Asserts. - * The reason is that if a backend's InitPostgres transaction fails (eg, - * because an invalid user name was given), we have to be able to get through - * AbortTransaction without asserting. - */ -void -AtAbort_UserId(void) -{ - CurrentUserId = OuterUserId; -} - - /* * Change session auth ID while running * @@ -593,9 +611,7 @@ GetUserNameFromId(Oid roleid) HeapTuple tuple; char *result; - tuple = SearchSysCache(AUTHOID, - ObjectIdGetDatum(roleid), - 0, 0, 0); + tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid)); if (!HeapTupleIsValid(tuple)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), @@ -613,18 +629,10 @@ GetUserNameFromId(Oid roleid) * * These routines are used to create both a data-directory lockfile * ($DATADIR/postmaster.pid) and a Unix-socket-file lockfile ($SOCKFILE.lock). - * Both kinds of files contain the same info: - * - * Owning process' PID - * Data directory path - * - * By convention, the owning process' PID is negated if it is a standalone - * backend rather than a postmaster. This is just for informational purposes. - * The path is also just for informational purposes (so that a socket lockfile - * can be more easily traced to the associated postmaster). - * - * A data-directory lockfile can optionally contain a third line, containing - * the key and ID for the shared memory block used by this postmaster. + * Both kinds of files contain the same info initially, although we can add + * more information to a data-directory lockfile after it's created, using + * AddToDataDirLockFile(). See miscadmin.h for documentation of the contents + * of these lockfiles. * * On successful lockfile creation, a proc_exit callback to remove the * lockfile is automatically created. @@ -661,12 +669,52 @@ CreateLockFile(const char *filename, bool amPostmaster, bool isDDLock, const char *refName) { int fd; - char buffer[MAXPGPATH + 100]; + char buffer[MAXPGPATH * 2 + 256]; int ntries; int len; int encoded_pid; pid_t other_pid; - pid_t my_pid = getpid(); + pid_t my_pid, + my_p_pid, + my_gp_pid; + const char *envvar; + + /* + * If the PID in the lockfile is our own PID or our parent's or + * grandparent's PID, then the file must be stale (probably left over from + * a previous system boot cycle). We need to check this because of the + * likelihood that a reboot will assign exactly the same PID as we had in + * the previous reboot, or one that's only one or two counts larger and + * hence the lockfile's PID now refers to an ancestor shell process. We + * allow pg_ctl to pass down its parent shell PID (our grandparent PID) + * via the environment variable PG_GRANDPARENT_PID; this is so that + * launching the postmaster via pg_ctl can be just as reliable as + * launching it directly. There is no provision for detecting + * further-removed ancestor processes, but if the init script is written + * carefully then all but the immediate parent shell will be root-owned + * processes and so the kill test will fail with EPERM. Note that we + * cannot get a false negative this way, because an existing postmaster + * would surely never launch a competing postmaster or pg_ctl process + * directly. + */ + my_pid = getpid(); + +#ifndef WIN32 + my_p_pid = getppid(); +#else + + /* + * Windows hasn't got getppid(), but doesn't need it since it's not using + * real kill() either... + */ + my_p_pid = 0; +#endif + + envvar = getenv("PG_GRANDPARENT_PID"); + if (envvar) + my_gp_pid = atoi(envvar); + else + my_gp_pid = 0; /* * We need a loop here because of race conditions. But don't loop forever @@ -728,17 +776,11 @@ CreateLockFile(const char *filename, bool amPostmaster, /* * Check to see if the other process still exists * - * If the PID in the lockfile is our own PID or our parent's PID, then - * the file must be stale (probably left over from a previous system - * boot cycle). We need this test because of the likelihood that a - * reboot will assign exactly the same PID as we had in the previous - * reboot. Also, if there is just one more process launch in this - * reboot than in the previous one, the lockfile might mention our - * parent's PID. We can reject that since we'd never be launched - * directly by a competing postmaster. We can't detect grandparent - * processes unfortunately, but if the init script is written - * carefully then all but the immediate parent shell will be - * root-owned processes and so the kill test will fail with EPERM. + * Per discussion above, my_pid, my_p_pid, and my_gp_pid can be + * ignored as false matches. + * + * Normally kill() will fail with ESRCH if the given PID doesn't + * exist. * * We can treat the EPERM-error case as okay because that error * implies that the existing process has a different userid than we @@ -755,18 +797,9 @@ CreateLockFile(const char *filename, bool amPostmaster, * Unix socket file belonging to an instance of Postgres being run by * someone else, at least on machines where /tmp hasn't got a * stickybit.) - * - * Windows hasn't got getppid(), but doesn't need it since it's not - * using real kill() either... - * - * Normally kill() will fail with ESRCH if the given PID doesn't - * exist. */ - if (other_pid != my_pid -#ifndef WIN32 - && other_pid != getppid() -#endif - ) + if (other_pid != my_pid && other_pid != my_p_pid && + other_pid != my_gp_pid) { if (kill(other_pid, 0) == 0 || (errno != ESRCH && errno != EPERM)) @@ -796,33 +829,39 @@ CreateLockFile(const char *filename, bool amPostmaster, * admin) but has left orphan backends behind. Check for this by * looking to see if there is an associated shmem segment that is * still in use. + * + * Note: because postmaster.pid is written in multiple steps, we might + * not find the shmem ID values in it; we can't treat that as an + * error. */ if (isDDLock) { - char *ptr; + char *ptr = buffer; unsigned long id1, id2; + int lineno; - ptr = strchr(buffer, '\n'); - if (ptr != NULL && - (ptr = strchr(ptr + 1, '\n')) != NULL) + for (lineno = 1; lineno < LOCK_FILE_LINE_SHMEM_KEY; lineno++) { + if ((ptr = strchr(ptr, '\n')) == NULL) + break; ptr++; - if (sscanf(ptr, "%lu %lu", &id1, &id2) == 2) - { - if (PGSharedMemoryIsInUse(id1, id2)) - ereport(FATAL, - (errcode(ERRCODE_LOCK_FILE_EXISTS), - errmsg("pre-existing shared memory block " - "(key %lu, ID %lu) is still in use", - id1, id2), - errhint("If you're sure there are no old " - "server processes still running, remove " - "the shared memory block with " - "the command \"ipcclean\", \"ipcrm\", " - "or just delete the file \"%s\".", - filename))); - } + } + + if (ptr != NULL && + sscanf(ptr, "%lu %lu", &id1, &id2) == 2) + { + if (PGSharedMemoryIsInUse(id1, id2)) + ereport(FATAL, + (errcode(ERRCODE_LOCK_FILE_EXISTS), + errmsg("pre-existing shared memory block " + "(key %lu, ID %lu) is still in use", + id1, id2), + errhint("If you're sure there are no old " + "server processes still running, remove " + "the shared memory block " + "or just delete the file \"%s\".", + filename))); } } @@ -842,11 +881,23 @@ CreateLockFile(const char *filename, bool amPostmaster, } /* - * Successfully created the file, now fill it. + * Successfully created the file, now fill it. See comment in miscadmin.h + * about the contents. Note that we write the same info into both datadir + * and socket lockfiles; although more stuff may get added to the datadir + * lockfile later. */ - snprintf(buffer, sizeof(buffer), "%d\n%s\n", + snprintf(buffer, sizeof(buffer), "%d\n%s\n%ld\n%d\n%s\n", amPostmaster ? (int) my_pid : -((int) my_pid), - DataDir); + DataDir, + (long) MyStartTime, + PostPortNumber, +#ifdef HAVE_UNIX_SOCKETS + (*UnixSocketDir != '\0') ? UnixSocketDir : DEFAULT_PGSOCKET_DIR +#else + "" +#endif + ); + errno = 0; if (write(fd, buffer, strlen(buffer)) != strlen(buffer)) { @@ -860,7 +911,18 @@ CreateLockFile(const char *filename, bool amPostmaster, (errcode_for_file_access(), errmsg("could not write lock file \"%s\": %m", filename))); } - if (close(fd)) + if (pg_fsync(fd) != 0) + { + int save_errno = errno; + + close(fd); + unlink(filename); + errno = save_errno; + ereport(FATAL, + (errcode_for_file_access(), + errmsg("could not write lock file \"%s\": %m", filename))); + } + if (close(fd) != 0) { int save_errno = errno; @@ -944,21 +1006,20 @@ TouchSocketLockFile(void) } } + /* - * Append information about a shared memory segment to the data directory - * lock file. + * Add (or replace) a line in the data directory lock file. + * The given string should not include a trailing newline. * - * This may be called multiple times in the life of a postmaster, if we - * delete and recreate shmem due to backend crash. Therefore, be prepared - * to overwrite existing information. (As of 7.1, a postmaster only creates - * one shm seg at a time; but for the purposes here, if we did have more than - * one then any one of them would do anyway.) + * Caution: this erases all following lines. In current usage that is OK + * because lines are added in order. We could improve it if needed. */ void -RecordSharedMemoryInLockFile(unsigned long id1, unsigned long id2) +AddToDataDirLockFile(int target_line, const char *str) { int fd; int len; + int lineno; char *ptr; char buffer[BLCKSZ]; @@ -971,7 +1032,7 @@ RecordSharedMemoryInLockFile(unsigned long id1, unsigned long id2) DIRECTORY_LOCK_FILE))); return; } - len = read(fd, buffer, sizeof(buffer) - 100); + len = read(fd, buffer, sizeof(buffer) - 1); if (len < 0) { ereport(LOG, @@ -984,23 +1045,24 @@ RecordSharedMemoryInLockFile(unsigned long id1, unsigned long id2) buffer[len] = '\0'; /* - * Skip over first two lines (PID and path). + * Skip over lines we are not supposed to rewrite. */ - ptr = strchr(buffer, '\n'); - if (ptr == NULL || - (ptr = strchr(ptr + 1, '\n')) == NULL) + ptr = buffer; + for (lineno = 1; lineno < target_line; lineno++) { - elog(LOG, "bogus data in \"%s\"", DIRECTORY_LOCK_FILE); - close(fd); - return; + if ((ptr = strchr(ptr, '\n')) == NULL) + { + elog(LOG, "bogus data in \"%s\"", DIRECTORY_LOCK_FILE); + close(fd); + return; + } + ptr++; } - ptr++; /* - * Append key information. Format to try to keep it the same length - * always (trailing junk won't hurt, but might confuse humans). + * Write or rewrite the target line. */ - sprintf(ptr, "%9lu %9lu\n", id1, id2); + snprintf(ptr, buffer + sizeof(buffer) - ptr, "%s\n", str); /* * And rewrite the data. Since we write in a single kernel call, this @@ -1021,7 +1083,14 @@ RecordSharedMemoryInLockFile(unsigned long id1, unsigned long id2) close(fd); return; } - if (close(fd)) + if (pg_fsync(fd) != 0) + { + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not write to file \"%s\": %m", + DIRECTORY_LOCK_FILE))); + } + if (close(fd) != 0) { ereport(LOG, (errcode_for_file_access(), @@ -1109,6 +1178,9 @@ ValidatePgVersion(const char *path) char *shared_preload_libraries_string = NULL; char *local_preload_libraries_string = NULL; +/* Flag telling that we are loading shared_preload_libraries */ +bool process_shared_preload_libraries_in_progress = false; + /* * load the shared libraries listed in 'libraries' * @@ -1120,6 +1192,7 @@ load_libraries(const char *libraries, const char *gucname, bool restricted) { char *rawstring; List *elemlist; + int elevel; ListCell *l; if (libraries == NULL || libraries[0] == '\0') @@ -1141,6 +1214,18 @@ load_libraries(const char *libraries, const char *gucname, bool restricted) return; } + /* + * Choose notice level: avoid repeat messages when re-loading a library + * that was preloaded into the postmaster. (Only possible in EXEC_BACKEND + * configurations) + */ +#ifdef EXEC_BACKEND + if (IsUnderPostmaster && process_shared_preload_libraries_in_progress) + elevel = DEBUG2; + else +#endif + elevel = LOG; + foreach(l, elemlist) { char *tok = (char *) lfirst(l); @@ -1160,7 +1245,7 @@ load_libraries(const char *libraries, const char *gucname, bool restricted) filename = expanded; } load_file(filename, restricted); - ereport(LOG, + ereport(elevel, (errmsg("loaded library \"%s\"", filename))); pfree(filename); } @@ -1175,9 +1260,11 @@ load_libraries(const char *libraries, const char *gucname, bool restricted) void process_shared_preload_libraries(void) { + process_shared_preload_libraries_in_progress = true; load_libraries(shared_preload_libraries_string, "shared_preload_libraries", false); + process_shared_preload_libraries_in_progress = false; } /* @@ -1190,3 +1277,18 @@ process_local_preload_libraries(void) "local_preload_libraries", true); } + +void +pg_bindtextdomain(const char *domain) +{ +#ifdef ENABLE_NLS + if (my_exec_path[0] != '\0') + { + char locale_path[MAXPGPATH]; + + get_locale_path(my_exec_path, locale_path); + bindtextdomain(domain, locale_path); + pg_bind_textdomain_codeset(domain); + } +#endif +}