/* ------------------------------------------------------------------------- * * miscinit.c * miscellaneous initialization support stuff * * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * src/backend/utils/init/miscinit.c * * ------------------------------------------------------------------------- */ #include "postgres.h" #include "knl/knl_variable.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_UTIME_H #include #endif #include #include "catalog/pg_authid.h" #include "job/job_scheduler.h" #include "job/job_worker.h" #include "mb/pg_wchar.h" #include "miscadmin.h" #include "pgstat.h" #include "postmaster/autovacuum.h" #include "postmaster/postmaster.h" #include "storage/fd.h" #include "storage/ipc.h" #include "storage/pg_shmem.h" #include "storage/proc.h" #include "storage/procarray.h" #include "utils/builtins.h" #include "utils/guc.h" #include "utils/memutils.h" #include "utils/syscache.h" #include "libpq/libpq.h" #include "auditfuncs.h" #include "replication/walsender.h" #include "alarm/alarm.h" #include "pgxc/pgxc.h" #include "pgxc/poolmgr.h" #include "catalog/pgxc_group.h" #include "optimizer/nodegroups.h" #include "utils/inval.h" #include "utils/lsyscache.h" #define DIRECTORY_LOCK_FILE "postmaster.pid" Alarm alarmItemTooManyDbUserConn[1] = {ALM_AI_Unknown, ALM_AS_Normal, 0, 0, 0, 0, {0}, {0}, NULL}; /* ---------------------------------------------------------------- * ignoring system indexes support stuff * * NOTE: "ignoring system indexes" means we do not use the system indexes * for lookups (either in hardwired catalog accesses or in planner-generated * plans). We do, however, still update the indexes when a catalog * modification is made. * ---------------------------------------------------------------- */ void ReportAlarmTooManyDbUserConn(const char* roleName) { AlarmAdditionalParam tempAdditionalParam; // Initialize the alarm item AlarmItemInitialize(alarmItemTooManyDbUserConn, ALM_AI_TooManyDbUserConn, alarmItemTooManyDbUserConn->stat, NULL, alarmItemTooManyDbUserConn->lastReportTime, alarmItemTooManyDbUserConn->reportCount); // fill the alarm message WriteAlarmAdditionalInfo(&tempAdditionalParam, g_instance.attr.attr_common.PGXCNodeName, "AllDatabases", const_cast(roleName), alarmItemTooManyDbUserConn, ALM_AT_Fault, const_cast(roleName)); // report the alarm AlarmReporter(alarmItemTooManyDbUserConn, ALM_AT_Fault, &tempAdditionalParam); } void ReportResumeTooManyDbUserConn(const char* roleName) { AlarmAdditionalParam tempAdditionalParam; // Initialize the alarm item AlarmItemInitialize(alarmItemTooManyDbUserConn, ALM_AI_TooManyDbUserConn, alarmItemTooManyDbUserConn->stat, NULL, alarmItemTooManyDbUserConn->lastReportTime, alarmItemTooManyDbUserConn->reportCount); // fill the resume message WriteAlarmAdditionalInfo(&tempAdditionalParam, g_instance.attr.attr_common.PGXCNodeName, "AllDatabases", const_cast(roleName), alarmItemTooManyDbUserConn, ALM_AT_Resume); // report the alarm AlarmReporter(alarmItemTooManyDbUserConn, ALM_AT_Resume, &tempAdditionalParam); } void ReportAlarmDataInstLockFileExist() { Alarm alarmItem[1]; AlarmAdditionalParam tempAdditionalParam; // Initialize the alarm item AlarmItemInitialize(alarmItem, ALM_AI_DataInstLockFileExist, ALM_AS_Reported, NULL); // fill the alarm message WriteAlarmAdditionalInfo(&tempAdditionalParam, g_instance.attr.attr_common.PGXCNodeName, "", "", alarmItem, ALM_AT_Fault, g_instance.attr.attr_common.PGXCNodeName); // report the alarm AlarmReporter(alarmItem, ALM_AT_Fault, &tempAdditionalParam); } void ReportResumeDataInstLockFileExist() { Alarm alarmItem[1]; AlarmAdditionalParam tempAdditionalParam; // Initialize the alarm item AlarmItemInitialize(alarmItem, ALM_AI_DataInstLockFileExist, ALM_AS_Normal, NULL); // fill the alarm message WriteAlarmAdditionalInfo( &tempAdditionalParam, g_instance.attr.attr_common.PGXCNodeName, "", "", alarmItem, ALM_AT_Resume); // report the alarm AlarmReporter(alarmItem, ALM_AT_Resume, &tempAdditionalParam); } /* ---------------------------------------------------------------- * database path / name support stuff * ---------------------------------------------------------------- */ void SetDatabasePath(const char* path) { /* This should happen only once per process */ Assert(!u_sess->proc_cxt.DatabasePath); u_sess->proc_cxt.DatabasePath = MemoryContextStrdup(u_sess->top_mem_cxt, path); } /* * Set data directory, but make sure it's an absolute path. Use this, * never set t_thrd.proc_cxt.DataDir directly. */ void SetDataDir(const char* dir) { AssertArg(dir); /* If presented path is relative, convert to absolute */ char* newm = make_absolute_path(dir); char real_newm[PATH_MAX] = {'\0'}; char* DataDir = (char*)MemoryContextAlloc(u_sess->top_mem_cxt, MAXPGPATH); if (realpath(newm, real_newm) == NULL) { ereport(ERROR, (errcode(ERRCODE_FILE_READ_FAILED),errmsg("invalid path:%s", dir))); } errno_t rc = strncpy_s(DataDir, MAXPGPATH, real_newm, strlen(real_newm)); securec_check(rc, "\0", "\0"); pfree(newm); if (t_thrd.proc_cxt.DataDir) { #ifdef FRONTEND free(t_thrd.proc_cxt.DataDir); #else pfree(t_thrd.proc_cxt.DataDir); #endif } t_thrd.proc_cxt.DataDir = DataDir; } /* * Change working directory to t_thrd.proc_cxt.DataDir. Most of the postmaster and backend * code assumes that we are in t_thrd.proc_cxt.DataDir so it can use relative paths to access * stuff in and under the data directory. For convenience during path * setup, however, we don't force the chdir to occur during SetDataDir. */ void ChangeToDataDir(void) { AssertState(t_thrd.proc_cxt.DataDir); if (chdir(t_thrd.proc_cxt.DataDir) < 0) { ereport(FATAL, (errcode_for_file_access(), errmsg("could not change directory to \"%s\": %m", t_thrd.proc_cxt.DataDir))); } } /* * If the given pathname isn't already absolute, make it so, interpreting * it relative to the current working directory. * * Also canonicalizes the path. The result is always a malloc'd copy. * * Note: interpretation of relative-path arguments during postmaster startup * should happen before doing ChangeToDataDir(), else the user will probably * not like the results. */ char* make_absolute_path(const char* path) { char* newm = NULL; size_t tmp_len; /* Returning null for null input is convenient for some callers */ if (path == NULL) { return NULL; } if (!is_absolute_path(path)) { char* buf = NULL; size_t buf_len = MAXPGPATH; for (;;) { #ifdef FRONTEND buf = (char*)malloc(buf_len); #else buf = (char*)MemoryContextAlloc(u_sess->top_mem_cxt, buf_len); #endif if (buf == NULL) { ereport(FATAL, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory"))); } if (getcwd(buf, buf_len) != NULL) { break; } else if (errno == ERANGE) { #ifdef FRONTEND free(buf); #else pfree(buf); #endif buf_len *= 2; continue; } else { #ifdef FRONTEND free(buf); #else pfree(buf); #endif ereport(FATAL, (errmsg("could not get current working directory: %m"))); } } tmp_len = strlen(buf) + strlen(path) + 2; #ifdef FRONTEND newm = (char*)malloc(tmp_len); #else newm = (char*)MemoryContextAlloc(u_sess->top_mem_cxt, tmp_len); #endif if (newm == NULL) { ereport(FATAL, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory"))); } int rcs = snprintf_s(newm, tmp_len, tmp_len - 1, "%s/%s", buf, path); securec_check_ss(rcs, "", ""); #ifdef FRONTEND free(buf); #else pfree(buf); #endif } else { newm = MemoryContextStrdup(u_sess->top_mem_cxt, path); if (newm == NULL) { ereport(FATAL, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory"))); } } /* Make sure punctuation is canonical, too */ canonicalize_path(newm); return newm; } /* * GetAuthenticatedUserId - get the authenticated user ID. */ Oid GetAuthenticatedUserId(void) { return u_sess->misc_cxt.AuthenticatedUserId; } /* * GetUserId - get the current effective user ID. * * Note: there's no SetUserId() anymore; use SetUserIdAndSecContext(). */ Oid GetUserId(void) { AssertState(OidIsValid(u_sess->misc_cxt.CurrentUserId)); return u_sess->misc_cxt.CurrentUserId; } /* Database Security: Support database audit */ /* * Brief : get the current effective user ID. * Description : called by all process. */ Oid GetCurrentUserId(void) { return u_sess->misc_cxt.CurrentUserId; } /* * GetOuterUserId/SetOuterUserId - get/set the outer-level user ID. */ Oid GetOuterUserId(void) { AssertState(OidIsValid(u_sess->misc_cxt.OuterUserId)); return u_sess->misc_cxt.OuterUserId; } static void SetOuterUserId(Oid user_id) { AssertState(u_sess->misc_cxt.SecurityRestrictionContext == 0); AssertArg(OidIsValid(user_id)); u_sess->misc_cxt.OuterUserId = user_id; /* We force the effective user ID to match, too */ u_sess->misc_cxt.CurrentUserId = user_id; } /* * GetSessionUserId/SetSessionUserId - get/set the session user ID. */ Oid GetSessionUserId(void) { AssertState(OidIsValid(u_sess->misc_cxt.SessionUserId)); return u_sess->misc_cxt.SessionUserId; } static void SetSessionUserId(Oid user_id, bool is_superuser) { AssertState(u_sess->misc_cxt.SecurityRestrictionContext == 0); AssertArg(OidIsValid(user_id)); u_sess->misc_cxt.SessionUserId = user_id; u_sess->misc_cxt.SessionUserIsSuperuser = is_superuser; u_sess->misc_cxt.SetRoleIsActive = false; /* We force the effective user IDs to match, too */ u_sess->misc_cxt.OuterUserId = user_id; u_sess->misc_cxt.CurrentUserId = user_id; /* update user id in MyBEEntry */ if (t_thrd.shemem_ptr_cxt.MyBEEntry != NULL) { t_thrd.shemem_ptr_cxt.MyBEEntry->st_userid = user_id; } } /* * modify_nodegroup_mode - get current node group mode; * The valid current_nodegroup_mode value is NG_COMMON and NG_LOGIC. * In datanode, current_nodegroup_mode value is always NG_COMMON because * pgxc_group has no record in datanode; */ void modify_nodegroup_mode() { if (t_thrd.proc_cxt.postgres_initialized && (IS_PGXC_COORDINATOR || isRestoreMode) && u_sess->misc_cxt.current_nodegroup_mode == NG_UNKNOWN) { HeapTuple htup; u_sess->misc_cxt.current_nodegroup_mode = NG_COMMON; htup = SearchSysCache1(PGXCGROUPNAME, CStringGetDatum(VNG_OPTION_ELASTIC_GROUP)); if (HeapTupleIsValid(htup)) { u_sess->misc_cxt.current_nodegroup_mode = NG_LOGIC; ReleaseSysCache(htup); } } } /* * in_logic_cluster - judge whether current session is in logic cluster mode. */ bool in_logic_cluster() { modify_nodegroup_mode(); return u_sess->misc_cxt.current_nodegroup_mode == NG_LOGIC; } /* * exist_logic_cluster - judge whether elastic_group exist. * The method is simiar as in_logic_cluster, but the overhead is greater than in_logic_cluster. * The function is independent of inval message. */ bool exist_logic_cluster() { if (!IS_PGXC_COORDINATOR) { return false; } HeapTuple htup; htup = SearchSysCache1(PGXCGROUPNAME, CStringGetDatum(VNG_OPTION_ELASTIC_GROUP)); if (HeapTupleIsValid(htup)) { ReleaseSysCache(htup); return true; } return false; } #ifdef ENABLE_MULTIPLE_NODES /* * show_nodegroup_mode - return node group mode as sting. * The function is only used in guc.cpp. */ const char* show_nodegroup_mode(void) { modify_nodegroup_mode(); switch (u_sess->misc_cxt.current_nodegroup_mode) { case NG_COMMON: return "node group"; case NG_LOGIC: return "logic cluster"; default: return "unknown"; } } #endif /* * get_current_lcgroup_name - get current logic group name. * The function return NULL in datanode because datanode don't see pgxc_group. */ const char* get_current_lcgroup_name() { if (IS_PGXC_COORDINATOR && u_sess->attr.attr_common.current_logic_cluster_name == NULL && OidIsValid(u_sess->misc_cxt.current_logic_cluster) && t_thrd.proc_cxt.postgres_initialized) { Form_pgxc_group rform; HeapTuple group_tup = SearchSysCache1(PGXCGROUPOID, ObjectIdGetDatum(u_sess->misc_cxt.current_logic_cluster)); if (HeapTupleIsValid(group_tup)) { rform = (Form_pgxc_group)GETSTRUCT(group_tup); u_sess->attr.attr_common.current_logic_cluster_name = MemoryContextStrdup(u_sess->top_mem_cxt, NameStr(rform->group_name)); ReleaseSysCache(group_tup); } } return u_sess->attr.attr_common.current_logic_cluster_name; } /* * set_current_lcgroup_oid - set current logic group oid. * The function is only used when SET ROLE and the session user is admin user. */ static void set_current_lcgroup_oid(Oid group_oid) { if (u_sess->misc_cxt.current_logic_cluster != group_oid && u_sess->attr.attr_common.current_logic_cluster_name != NULL) { pfree(u_sess->attr.attr_common.current_logic_cluster_name); u_sess->attr.attr_common.current_logic_cluster_name = NULL; } u_sess->misc_cxt.current_logic_cluster = group_oid; modify_nodegroup_mode(); (void)get_current_lcgroup_name(); } /* * show_show_lcgroup_name - show current logic group name. * The function is only used in guc.cpp. */ const char* show_lcgroup_name() { const char* name = get_current_lcgroup_name(); return name == NULL ? "" : name; } /* * get_current_lcgroup_oid - get current logic group oid. * The logic group oid will not be changed once login for common user. * sysadmin user (superuser) is not attached to any logic group, so the * logic group oid is InvalidOid when sysadmin user login. * SET ROLE will change to session logic group oid and * RESET ROLE will restore to InvalidOid for sysadmin user. */ Oid get_current_lcgroup_oid() { return u_sess->misc_cxt.current_logic_cluster; } /* * is_lcgroup_admin - judge whether current user is logic cluster administrator. * Notice logic cluster administrator is not system admin user. */ bool is_lcgroup_admin() { if (IS_PGXC_COORDINATOR && (!OidIsValid(u_sess->misc_cxt.CurrentUserId) || !OidIsValid(u_sess->misc_cxt.current_logic_cluster))) { return false; } return has_rolvcadmin(u_sess->misc_cxt.CurrentUserId); } /* * is_logic_cluster - judge whether the node group is logic cluster. * The logic cluster's group_kind is 'v' or 'e'; * NOTICE: the function think elastic_group as logic cluster. */ bool is_logic_cluster(Oid group_id) { Datum datum; bool is_null = false; HeapTuple tup; char group_kind; tup = SearchSysCache1(PGXCGROUPOID, ObjectIdGetDatum(group_id)); if (HeapTupleIsValid(tup)) { datum = SysCacheGetAttr(PGXCGROUPOID, tup, Anum_pgxc_group_kind, &is_null); if (!is_null) { group_kind = DatumGetChar(datum); if (group_kind == 'v' || group_kind == 'e') { ReleaseSysCache(tup); return true; } } ReleaseSysCache(tup); } return false; } /* * get_pgxc_logic_groupoid * Obtain PGXC Logic Group Oid for roleid * Return Invalid Oid if group does not exist */ Oid get_pgxc_logic_groupoid(Oid role_id) { Datum acl_datum; Oid group_id; HeapTuple role_tup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(role_id)); if (!HeapTupleIsValid(role_tup)) { return InvalidOid; } bool is_null = true; acl_datum = SysCacheGetAttr(AUTHOID, role_tup, Anum_pg_authid_rolnodegroup, &is_null); group_id = is_null ? InvalidOid : DatumGetObjectId(acl_datum); ReleaseSysCache(role_tup); return group_id; } /* * get_pgxc_logic_groupoid * Obtain PGXC Logic Group Oid for rolename * Return Invalid Oid if group does not exist */ Oid get_pgxc_logic_groupoid(const char* role_name) { bool is_null = false; HeapTuple role_tup = SearchSysCache1(AUTHNAME, CStringGetDatum(role_name)); if (!HeapTupleIsValid(role_tup)) { return InvalidOid; } is_null = true; Datum acl_datum = SysCacheGetAttr(AUTHOID, role_tup, Anum_pg_authid_rolnodegroup, &is_null); Oid group_id = is_null ? InvalidOid : DatumGetObjectId(acl_datum); ReleaseSysCache(role_tup); return group_id; } /* * NodeGroupCallback * Syscache inval callback function */ static void NodeGroupCallback(Datum arg, int cache_id, uint32 hash_value) { u_sess->misc_cxt.current_nodegroup_mode = NG_UNKNOWN; RelationCacheInvalidateBuckets(); } /* * RegisterNodeGroupCacheCallback * Register pgxc_group change callback, only for coordinator. */ static void RegisterNodeGroupCacheCallback() { if (IS_PGXC_COORDINATOR && !u_sess->misc_cxt.nodegroup_callback_registered) { CacheRegisterSyscacheCallback(PGXCGROUPOID, NodeGroupCallback, (Datum)0); u_sess->misc_cxt.nodegroup_callback_registered = true; } } /* * GetUserIdAndSecContext/SetUserIdAndSecContext - get/set the current user ID * and the u_sess->misc_cxt.SecurityRestrictionContext flags. * * Currently there are two valid bits in u_sess->misc_cxt.SecurityRestrictionContext: * * SECURITY_LOCAL_USERID_CHANGE indicates that we are inside an operation * that is temporarily changing u_sess->misc_cxt.CurrentUserId via these functions. This is * needed to indicate that the actual value of u_sess->misc_cxt.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 u_sess->misc_cxt.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* user_id, int* sec_context) { *user_id = u_sess->misc_cxt.CurrentUserId; *sec_context = u_sess->misc_cxt.SecurityRestrictionContext; } void SetUserIdAndSecContext(Oid user_id, int sec_context) { u_sess->misc_cxt.CurrentUserId = user_id; u_sess->misc_cxt.SecurityRestrictionContext = sec_context; } /* * InLocalUserIdChange - are we inside a local change of u_sess->misc_cxt.CurrentUserId? */ bool InLocalUserIdChange(void) { return (u_sess->misc_cxt.SecurityRestrictionContext & SECURITY_LOCAL_USERID_CHANGE) != 0; } /* * InSecurityRestrictedOperation - are we inside a security-restricted command? */ bool InSecurityRestrictedOperation(void) { return (u_sess->misc_cxt.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* user_id, bool* sec_def_context) { *user_id = u_sess->misc_cxt.CurrentUserId; *sec_def_context = InLocalUserIdChange(); } void SetUserIdAndContext(Oid user_id, 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"))); } u_sess->misc_cxt.CurrentUserId = user_id; if (sec_def_context) { u_sess->misc_cxt.SecurityRestrictionContext |= SECURITY_LOCAL_USERID_CHANGE; } else { u_sess->misc_cxt.SecurityRestrictionContext &= ~SECURITY_LOCAL_USERID_CHANGE; } } /* * Check whether specified role has explicit REPLICATION privilege */ bool has_rolreplication(Oid role_id) { bool result = false; HeapTuple utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(role_id)); if (HeapTupleIsValid(utup)) { result = ((Form_pg_authid)GETSTRUCT(utup))->rolreplication; ReleaseSysCache(utup); } return result; } /* * Check whether specified role has explicit vcadmin privilege */ bool has_rolvcadmin(Oid role_id) { bool is_null = false; bool result = false; Datum datum; HeapTuple utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(role_id)); if (HeapTupleIsValid(utup)) { datum = SysCacheGetAttr(AUTHOID, utup, Anum_pg_authid_rolkind, &is_null); if (!is_null) { result = (DatumGetChar(datum) == 'v'); } ReleaseSysCache(utup); } return result; } /* * Initialize user identity during normal backend startup */ void InitializeSessionUserId(const char* role_name, Oid role_id) { HeapTuple role_tup; Form_pg_authid rform; char* rname = NULL; /* Audit user login */ char details[PGAUDIT_MAXLENGTH]; /* * Don't do scans if we're bootstrapping, none of the system catalogs * exist yet, and they should be owned by postgres anyway. */ AssertState(!IsBootstrapProcessingMode()); /* In pooler stateless reuse mode, to reset session userid */ if (!g_instance.attr.attr_network.PoolerStatelessReuse) { /* call only once */ AssertState(!OidIsValid(u_sess->misc_cxt.AuthenticatedUserId)); } if (role_name != NULL) { role_tup = SearchSysCache1(AUTHNAME, PointerGetDatum(role_name)); if (!HeapTupleIsValid(role_tup)) { /* Audit user login */ int rcs = snprintf_truncated_s(details, sizeof(details), "login db(%s) failed-the role(%s)does not exist", u_sess->proc_cxt.MyProcPort->database_name, role_name); securec_check_ss(rcs, "", ""); pgaudit_user_login(FALSE, u_sess->proc_cxt.MyProcPort->database_name, details); ereport(FATAL, (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), errmsg("Invalid username/password,login denied."))); } } else { role_tup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(role_id)); if (!HeapTupleIsValid(role_tup)) { ereport(FATAL, (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), errmsg("role with OID %u does not exist", role_id))); } } rform = (Form_pg_authid)GETSTRUCT(role_tup); role_id = HeapTupleGetOid(role_tup); rname = NameStr(rform->rolname); u_sess->misc_cxt.AuthenticatedUserId = role_id; u_sess->misc_cxt.AuthenticatedUserIsSuperuser = rform->rolsuper; u_sess->proc_cxt.MyRoleId = role_id; /* This sets u_sess->misc_cxt.OuterUserId/u_sess->misc_cxt.CurrentUserId too */ SetSessionUserId(role_id, u_sess->misc_cxt.AuthenticatedUserIsSuperuser); RegisterNodeGroupCacheCallback(); if (IS_PGXC_COORDINATOR) { Datum group_datum; bool is_null = true; group_datum = SysCacheGetAttr(AUTHOID, role_tup, Anum_pg_authid_rolnodegroup, &is_null); u_sess->misc_cxt.current_logic_cluster = is_null ? InvalidOid : DatumGetObjectId(group_datum); } /* Also mark our PGPROC entry with the authenticated user id */ /* (We assume this is an atomic store so no lock is needed) */ t_thrd.proc->roleId = role_id; /* * 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;". */ if (IsUnderPostmaster) { /* * Is role allowed to login at all? */ if (!rform->rolcanlogin) { ereport(FATAL, (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), errmsg("role \"%s\" is not permitted to login", role_name))); } /* * Check connection limit for this role. * * There is a race condition here --- we create our PGPROC before * checking for other PGPROCs. If two backends did this at about the * same time, they might both think they were over the limit, while * ideally one should succeed and one fail. Getting that to work * exactly seems more trouble than it is worth, however; instead we * just document that the connection limit is approximate. */ if (rform->rolconnlimit >= 0 && !u_sess->misc_cxt.AuthenticatedUserIsSuperuser && CountUserBackends(role_id) > rform->rolconnlimit) { ReportAlarmTooManyDbUserConn(role_name); ereport(FATAL, (errcode(ERRCODE_TOO_MANY_CONNECTIONS), errmsg("too many connections for role \"%s\"", role_name))); } else if (!u_sess->misc_cxt.AuthenticatedUserIsSuperuser) { ReportResumeTooManyDbUserConn(role_name); } } /* Record username and superuser status as GUC settings too */ SetConfigOption("session_authorization", role_name, PGC_BACKEND, PGC_S_OVERRIDE); SetConfigOption( "is_sysadmin", u_sess->misc_cxt.AuthenticatedUserIsSuperuser ? "on" : "off", PGC_INTERNAL, PGC_S_OVERRIDE); ReleaseSysCache(role_tup); } /* * Initialize user identity during special backend startup */ void InitializeSessionUserIdStandalone(void) { /* * This function should only be called in single-user mode and in * autovacuum workers, and in background workers. */ AssertState(!IsUnderPostmaster || IsAutoVacuumWorkerProcess() || IsJobSchedulerProcess() || IsJobWorkerProcess() || AM_WAL_SENDER || IsBackgroundWorker); /* In pooler stateless reuse mode, to reset session userid */ if (!g_instance.attr.attr_network.PoolerStatelessReuse) { /* call only once */ AssertState(!OidIsValid(u_sess->misc_cxt.AuthenticatedUserId)); } u_sess->misc_cxt.AuthenticatedUserId = BOOTSTRAP_SUPERUSERID; u_sess->misc_cxt.AuthenticatedUserIsSuperuser = true; SetSessionUserId(BOOTSTRAP_SUPERUSERID, true); RegisterNodeGroupCacheCallback(); } /* * Change session auth ID while running * * Only a superuser may set auth ID to something other than himself. Note * that in case of multiple SETs in a single session, the original userid's * superuserness is what matters. But we set the GUC variable is_superuser * to indicate whether the *current* session userid is a superuser. * * Note: this is not an especially clean place to do the permission check. * It's OK because the check does not require catalog access and can't * fail during an end-of-transaction GUC reversion, but we may someday * have to push it up into assign_session_authorization. */ void SetSessionAuthorization(Oid user_id, bool is_superuser) { /* Must have authenticated already, else can't make permission check */ AssertState(OidIsValid(u_sess->misc_cxt.AuthenticatedUserId)); if (!t_thrd.xact_cxt.bInAbortTransaction && user_id != u_sess->misc_cxt.AuthenticatedUserId && !u_sess->misc_cxt.AuthenticatedUserIsSuperuser && !superuser()) { ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied to set session authorization"))); } SetSessionUserId(user_id, is_superuser); SetConfigOption("is_sysadmin", is_superuser ? "on" : "off", PGC_INTERNAL, PGC_S_OVERRIDE); } /* * Report current role id * This follows the semantics of SET ROLE, ie return the outer-level ID * not the current effective ID, and return InvalidOid when the setting * is logically SET ROLE NONE. */ Oid GetCurrentRoleId(void) { if (u_sess->misc_cxt.SetRoleIsActive) { return u_sess->misc_cxt.OuterUserId; } else { return InvalidOid; } } /* * Change Role ID while running (SET ROLE) * * If roleid is InvalidOid, we are doing SET ROLE NONE: revert to the * session user authorization. In this case the is_superuser argument * is ignored. * * When roleid is not InvalidOid, the caller must have checked whether * the session user has permission to become that role. (We cannot check * here because this routine must be able to execute in a failed transaction * to restore a prior value of the ROLE GUC variable.) */ void SetCurrentRoleId(Oid role_id, bool is_superuser) { /* * Get correct info if it's SET ROLE NONE * * If u_sess->misc_cxt.SessionUserId hasn't been set yet, just do nothing --- the eventual * SetSessionUserId call will fix everything. This is needed since we * will get called during GUC initialization. */ if (!OidIsValid(role_id)) { if (!OidIsValid(u_sess->misc_cxt.SessionUserId)) { return; } role_id = u_sess->misc_cxt.SessionUserId; is_superuser = u_sess->misc_cxt.SessionUserIsSuperuser; u_sess->misc_cxt.SetRoleIsActive = false; } else { u_sess->misc_cxt.SetRoleIsActive = true; } SetOuterUserId(role_id); SetConfigOption("is_sysadmin", is_superuser ? "on" : "off", PGC_INTERNAL, PGC_S_OVERRIDE); if (u_sess->misc_cxt.SessionUserIsSuperuser) { set_current_lcgroup_oid(OidIsValid(role_id) ? get_pgxc_logic_groupoid(role_id) : InvalidOid); } } /* * Get user name from user oid */ char* GetUserNameFromId(Oid role_id) { HeapTuple tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(role_id)); if (!HeapTupleIsValid(tuple)) { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("invalid role OID: %u", role_id))); } char* result = pstrdup(NameStr(((Form_pg_authid)GETSTRUCT(tuple))->rolname)); ReleaseSysCache(tuple); return result; } /* ------------------------------------------------------------------------- * Interlock-file support * * 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 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. * ------------------------------------------------------------------------- * * proc_exit callback to remove a lockfile. */ static void UnlinkLockFile(int status, Datum file_name) { char* f_name = (char*)DatumGetPointer(file_name); if (f_name != NULL) { if (unlink(f_name) != 0) { /* Should we complain if the unlink fails? */ ereport(LOG, (errmsg("unlink lockfile %s fails.", f_name))); } pfree(f_name); } } /* * Create a lockfile. * * filename is the name of the lockfile to create. * amPostmaster is used to determine how to encode the output PID. * isDDLock and refName are used to determine what error message to produce. */ static void CreateLockFile(const char* file_name, bool am_post_master, bool is_dd_lock, const char* ref_name) { int fd = -1; char buffer[MAXPGPATH * 2 + 256]; int ntries; int len; int encoded_pid; pid_t other_pid; pid_t my_pid; pid_t my_p_pid; pid_t my_gp_pid; const char* envvar = NULL; /* * 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 = gs_getenv_r("PG_GRANDPARENT_PID"); if (envvar != NULL) { check_backend_env(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 * (for example, a non-writable $PGDATA directory might cause a failure * that won't go away). 100 tries seems like plenty. */ for (ntries = 0;; ntries++) { /* * Try to create the lock file --- O_EXCL makes this atomic. * * Think not to make the file protection weaker than 0600. See * comments below. */ fd = open(file_name, O_RDWR | O_CREAT | O_EXCL, 0600); if (fd >= 0) { break; /* Success; exit the retry loop */ } /* * Couldn't create the pid file. Probably it already exists. */ if ((errno != EEXIST && errno != EACCES) || ntries > 100) { ereport(FATAL, (errcode_for_file_access(), errmsg("could not create lock file \"%s\": %m", file_name))); } /* * Read the file to get the old owner's PID. Note race condition * here: file might have been deleted since we tried to create it. */ fd = open(file_name, O_RDONLY, 0600); if (fd < 0) { if (errno == ENOENT) { continue; /* race condition; try again */ } ereport(FATAL, (errcode_for_file_access(), errmsg("could not open lock file \"%s\": %m", file_name))); } pgstat_report_waitevent(WAIT_EVENT_LOCK_FILE_CREATE_READ); if ((len = read(fd, buffer, sizeof(buffer) - 1)) < 0) { ereport(FATAL, (errcode_for_file_access(), errmsg("could not read lock file \"%s\": %m", file_name))); } pgstat_report_waitevent(WAIT_EVENT_END); close(fd); buffer[len] = '\0'; encoded_pid = atoi(buffer); /* if pid < 0, the pid is for postgres, not postmaster */ other_pid = (pid_t)(encoded_pid < 0 ? -encoded_pid : encoded_pid); if (other_pid <= 0) { struct stat file_name_stat; if (lstat(file_name, &file_name_stat) >= 0) { if (0 == file_name_stat.st_size) { if (remove(file_name) < 0) ereport(FATAL, (errcode_for_file_access(), errmsg("bogus lock file \"%s\",could not unlink it : %m", file_name))); continue; } } ereport(FATAL, (errmsg("bogus data in lock file \"%s\": \"%s\", please kill the " "instance process, than remove the damaged lock file", file_name, buffer))); } /* * Check to see if the other process still exists * * 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 * do, which means it cannot be a competing postmaster. A postmaster * cannot successfully attach to a data directory owned by a userid * other than its own. (This is now checked directly in * checkDataDir(), but has been true for a long time because of the * restriction that the data directory isn't group- or * world-accessible.) Also, since we create the lockfiles mode 600, * we'd have failed above if the lockfile belonged to another userid * --- which means that whatever process kill() is reporting about * isn't the one that made the lockfile. (NOTE: this last * consideration is the only one that keeps us from blowing away a * 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.) */ 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)) { /* lockfile belongs to a live process */ #ifndef WIN32 if (IsMyPostmasterPid(other_pid, t_thrd.proc_cxt.DataDir)) #endif { ReportAlarmDataInstLockFileExist(); ereport(FATAL, (errcode(ERRCODE_LOCK_FILE_EXISTS), errmsg("lock file \"%s\" already exists", file_name), is_dd_lock ? (encoded_pid < 0 ? errhint("Is another postgres (PID %d) running in data directory \"%s\"?", (int)other_pid, ref_name) : errhint("Is another postmaster (PID %d) running in data directory \"%s\"?", (int)other_pid, ref_name)) : (encoded_pid < 0 ? errhint("Is another postgres (PID %d) using socket file \"%s\"?", (int)other_pid, ref_name) : errhint("Is another postmaster (PID %d) using socket file \"%s\"?", (int)other_pid, ref_name)))); } } } /* * No, the creating process did not exist. However, it could be that * the postmaster crashed (or more likely was kill -9'd by a clueless * 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 (is_dd_lock != false) { char* ptr = buffer; unsigned long id1; unsigned long id2; for (int line_no = 1; line_no < LOCK_FILE_LINE_SHMEM_KEY; line_no++) { if ((ptr = strchr(ptr, '\n')) == NULL) { break; } ptr++; } if (ptr != NULL && sscanf_s(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\".", file_name))); } } } /* * Looks like nobody's home. Unlink the file and try again to create * it. Need a loop because of possible race condition against other * would-be creators. */ if (unlink(file_name) < 0) { ereport(FATAL, (errcode_for_file_access(), errmsg("could not remove old lock file \"%s\": %m", file_name), errhint("The file seems accidentally left over, but " "it could not be removed. Please remove the file by hand and try again."))); } } ReportResumeDataInstLockFileExist(); /* * Successfully created the file, now fill it. See comment in miscadmin.h * about the contents. Note that we write the same first five lines into * both datadir and socket lockfiles; although more stuff may get added to * the datadir lockfile later. */ char* unix_socket_dir = NULL; char* pg_host = gs_getenv_r("PGHOST"); if (pg_host != NULL) { check_backend_env(pg_host); } if (*g_instance.attr.attr_network.UnixSocketDir != '\0') { unix_socket_dir = g_instance.attr.attr_network.UnixSocketDir; } else { if (pg_host != NULL && *(pg_host) != '\0') { unix_socket_dir = pg_host; } else { unix_socket_dir = DEFAULT_PGSOCKET_DIR; } } int rc = snprintf_s(buffer, sizeof(buffer), sizeof(buffer) - 1, "%d\n%s\n%ld\n%d\n%s\n", am_post_master ? (int)my_pid : -((int)my_pid), t_thrd.proc_cxt.DataDir, (long)t_thrd.proc_cxt.MyStartTime, g_instance.attr.attr_network.PostPortNumber, #ifdef HAVE_UNIX_SOCKETS unix_socket_dir #else "" #endif ); securec_check_ss(rc, "", ""); /* * In a standalone backend, the next line (LOCK_FILE_LINE_LISTEN_ADDR) * will never receive data, so fill it in as empty now. */ if (is_dd_lock && !am_post_master) { strlcat(buffer, "\n", sizeof(buffer)); } errno = 0; pgstat_report_waitevent(WAIT_EVENT_LOCK_FILE_CREATE_WRITE); if ((unsigned int)(write(fd, buffer, strlen(buffer))) != strlen(buffer)) { int save_errno = errno; close(fd); (void)unlink(file_name); /* if write didn't set errno, assume problem is no disk space */ errno = save_errno ? save_errno : ENOSPC; ereport(FATAL, (errcode_for_file_access(), errmsg("could not write lock file \"%s\": %m", file_name))); } pgstat_report_waitevent(WAIT_EVENT_END); pgstat_report_waitevent(WAIT_EVENT_LOCK_FILE_CREATE_SYNC); if (pg_fsync(fd) != 0) { int save_errno = errno; close(fd); (void)unlink(file_name); errno = save_errno; ereport(FATAL, (errcode_for_file_access(), errmsg("could not write lock file \"%s\": %m", file_name))); } pgstat_report_waitevent(WAIT_EVENT_END); if (close(fd) != 0) { int save_errno = errno; (void)unlink(file_name); errno = save_errno; ereport(FATAL, (errcode_for_file_access(), errmsg("could not write lock file \"%s\": %m", file_name))); } /* * Arrange for automatic removal of lockfile at proc_exit. */ { char* ptr = NULL; ptr = MemoryContextStrdup(u_sess->top_mem_cxt, file_name); on_proc_exit(UnlinkLockFile, PointerGetDatum(ptr)); } } /* * Create the data directory lockfile. * * When this is called, we must have already switched the working * directory to t_thrd.proc_cxt.DataDir, so we can just use a relative path. This * helps ensure that we are locking the directory we should be. */ void CreateDataDirLockFile(bool am_post_master) { CreateLockFile(DIRECTORY_LOCK_FILE, am_post_master, true, t_thrd.proc_cxt.DataDir); } /* * Create a lockfile for the specified Unix socket file. */ void CreateSocketLockFile(const char* socket_file, bool am_post_master, bool is_create_psql_sock) { char lock_file[MAXPGPATH]; int rc = snprintf_s(lock_file, sizeof(lock_file), sizeof(lock_file) - 1, "%s.lock", socket_file); securec_check_ss(rc, "", ""); CreateLockFile(lock_file, am_post_master, false, socket_file); /* Save name of lock_file for TouchSocketLockFile */ errno_t rcs = strcpy_s((is_create_psql_sock ? u_sess->misc_cxt.socketLockFile : u_sess->misc_cxt.hasocketLockFile), MAXPGPATH, lock_file); securec_check_c(rcs, "", ""); } /* * TouchSocketLockFileInternel & TouchSocketLockFile -- mark socket lock file as recently accessed * * This routine should be called every so often to ensure that the lock file * has a recent mod or access date. That saves it * from being removed by overenthusiastic /tmp-directory-cleaner daemons. * (Another reason we should never have put the socket file in /tmp...) */ void TouchSocketLockFileInternel(const char* socket_lock_file) { /* Do nothing if we did not create a socket... */ if (socket_lock_file[0] != '\0') { /* * utime() is POSIX standard, utimes() is a common alternative; if we * have neither, fall back to actually reading the file (which only * sets the access time not mod time, but that should be enough in * most cases). In all paths, we ignore errors. */ #ifdef HAVE_UTIME utime(socket_lock_file, NULL); #else /* !HAVE_UTIME */ #ifdef HAVE_UTIMES utimes(socket_lock_file, NULL); #else /* !HAVE_UTIMES */ int fd = -1; char buffer[1]; fd = open(socket_lock_file, O_RDONLY | PG_BINARY, 0); if (fd >= 0) { read(fd, buffer, sizeof(buffer)); close(fd); } #endif /* HAVE_UTIMES */ #endif /* HAVE_UTIME */ } } void TouchSocketLockFile(void) { TouchSocketLockFileInternel(u_sess->misc_cxt.socketLockFile); TouchSocketLockFileInternel(u_sess->misc_cxt.hasocketLockFile); } /* * Add (or replace) a line in the data directory lock file. * The given string should not include a trailing newline. * * 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 AddToDataDirLockFile(int target_line, const char* str) { int fd = -1; int len; int line_no; char* ptr = NULL; char buffer[BLCKSZ]; fd = open(DIRECTORY_LOCK_FILE, O_RDWR | PG_BINARY, 0); if (fd < 0) { ereport(LOG, (errcode_for_file_access(), errmsg("could not open file \"%s\": %m", DIRECTORY_LOCK_FILE))); return; } pgstat_report_waitevent(WAIT_EVENT_LOCK_FILE_ADDTODATADIR_READ); len = read(fd, buffer, sizeof(buffer) - 1); pgstat_report_waitevent(WAIT_EVENT_END); if (len < 0) { ereport(LOG, (errcode_for_file_access(), errmsg("could not read from file \"%s\": %m", DIRECTORY_LOCK_FILE))); close(fd); return; } buffer[len] = '\0'; /* * Skip over lines we are not supposed to rewrite. */ ptr = buffer; for (line_no = 1; line_no < target_line; line_no++) { if ((ptr = strchr(ptr, '\n')) == NULL) { ereport(LOG, (errmsg("incomplete data in \"%s\": found only %d newlines while trying to add line %d", DIRECTORY_LOCK_FILE, line_no - 1, target_line))); close(fd); return; } ptr++; } /* * Write or rewrite the target line. */ int rcs = snprintf_s(ptr, buffer + sizeof(buffer) - ptr, buffer + sizeof(buffer) - ptr - 1, "%s\n", str); securec_check_ss(rcs, "", ""); /* * And rewrite the data. Since we write in a single kernel call, this * update should appear atomic to onlookers. */ len = strlen(buffer); errno = 0; pgstat_report_waitevent(WAIT_EVENT_LOCK_FILE_ADDTODATADIR_WRITE); if (lseek(fd, (off_t)0, SEEK_SET) != 0 || (int)write(fd, buffer, len) != len) { pgstat_report_waitevent(WAIT_EVENT_END); /* if write didn't set errno, assume problem is no disk space */ if (errno == 0) { errno = ENOSPC; } ereport(LOG, (errcode_for_file_access(), errmsg("could not write to file \"%s\": %m", DIRECTORY_LOCK_FILE))); close(fd); return; } pgstat_report_waitevent(WAIT_EVENT_END); pgstat_report_waitevent(WAIT_EVENT_LOCK_FILE_ADDTODATADIR_SYNC); if (pg_fsync(fd) != 0) { ereport(LOG, (errcode_for_file_access(), errmsg("could not write to file \"%s\": %m", DIRECTORY_LOCK_FILE))); } pgstat_report_waitevent(WAIT_EVENT_END); if (close(fd) != 0) { ereport(LOG, (errcode_for_file_access(), errmsg("could not write to file \"%s\": %m", DIRECTORY_LOCK_FILE))); } } /* ------------------------------------------------------------------------- * Version checking support * ------------------------------------------------------------------------- * * Determine whether the PG_VERSION file in directory `path' indicates * a data version compatible with the version of this program. * * If compatible, return. Otherwise, ereport(FATAL). */ void ValidatePgVersion(const char* path) { char full_path[MAXPGPATH]; FILE* file = NULL; int ret; long file_major; long file_minor; long my_major = 0; long my_minor = 0; char* endptr = NULL; const char* version_string = PG_VERSION; errno_t rc; my_major = strtol(version_string, &endptr, 10); if (*endptr == '.') { my_minor = strtol(endptr + 1, NULL, 10); } rc = snprintf_s(full_path, sizeof(full_path), sizeof(full_path) - 1, "%s/PG_VERSION", path); securec_check_intval(rc, , ); file = AllocateFile(full_path, "r"); if (file == NULL) { if (errno == ENOENT) { ereport(FATAL, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("\"%s\" is not a valid data directory", path), errdetail("File \"%s\" is missing.", full_path))); } else { ereport(FATAL, (errcode_for_file_access(), errmsg("could not open file \"%s\": %m", full_path))); } } ret = fscanf_s(file, "%ld.%ld", &file_major, &file_minor); if (ret != 2) { ereport(FATAL, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("\"%s\" is not a valid data directory", path), errdetail("File \"%s\" does not contain valid data.", full_path), errhint("You might need to initdb."))); } (void)FreeFile(file); if (my_major != file_major || my_minor != file_minor) { ereport(FATAL, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("database files are incompatible with server"), errdetail("The data directory was initialized by PostgreSQL version %ld.%ld, " "which is not compatible with this version %s.", file_major, file_minor, version_string))); } } /* ------------------------------------------------------------------------- * Library preload support * ------------------------------------------------------------------------- * * load the shared libraries listed in 'libraries' * * 'gucname': name of GUC variable, for error reports * 'restricted': if true, force libraries to be in $libdir/plugins/ */ static void load_libraries(const char* libraries, const char* guc_name, bool restricted) { char* raw_string = NULL; List* elem_list = NULL; int elevel; ListCell* l = NULL; if (libraries == NULL || libraries[0] == '\0') { return; /* nothing to do */ } /* Need a modifiable copy of string */ raw_string = pstrdup(libraries); /* Parse string into list of identifiers */ if (!SplitIdentifierString(raw_string, ',', &elem_list)) { /* syntax error in list */ pfree(raw_string); list_free(elem_list); ereport(LOG, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("invalid list syntax in parameter \"%s\"", guc_name))); return; } /* * Choose notice level: avoid repeat messages when re-loading a library * that was preloaded into the postmaster. (Only possible in EXEC_BACKEND * configurations) */ elevel = LOG; #ifdef EXEC_BACKEND if (IsUnderPostmaster && u_sess->misc_cxt.process_shared_preload_libraries_in_progress) { elevel = DEBUG2; } #endif foreach (l, elem_list) { char* tok = (char*)lfirst(l); char* file_name = NULL; errno_t rc; /* If restricting, insert $libdir/plugins if not mentioned already */ file_name = pstrdup(tok); canonicalize_path(file_name); if (restricted && first_dir_separator(file_name) == NULL) { char* expanded = NULL; expanded = (char*)palloc(strlen("$libdir/plugins/") + strlen(file_name) + 1); rc = strcpy_s(expanded, strlen("$libdir/plugins/") + strlen(file_name) + 1, "$libdir/plugins/"); securec_check_c(rc, "", ""); rc = strcat_s(expanded, strlen("$libdir/plugins/") + strlen(file_name) + 1, file_name); securec_check_c(rc, "", ""); pfree(file_name); file_name = expanded; } load_file(file_name, restricted); ereport(elevel, (errmsg("loaded library \"%s\"", file_name))); pfree(file_name); } pfree(raw_string); list_free(elem_list); } /* * process any libraries that should be preloaded at postmaster start */ void process_shared_preload_libraries(void) { u_sess->misc_cxt.process_shared_preload_libraries_in_progress = true; load_libraries(g_instance.attr.attr_common.shared_preload_libraries_string, "shared_preload_libraries", false); u_sess->misc_cxt.process_shared_preload_libraries_in_progress = false; } /* * process any libraries that should be preloaded at backend start */ void process_local_preload_libraries(void) { load_libraries(u_sess->attr.attr_common.local_preload_libraries_string, "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 } // reset u_sess->misc_cxt.Pseudo_CurrentUserId's reference to u_sess->misc_cxt.CurrentUserId, always called // when the stored procedure completed. void Reset_Pseudo_CurrentUserId(void) { u_sess->misc_cxt.Pseudo_CurrentUserId = &u_sess->misc_cxt.CurrentUserId; }