diff options
Diffstat (limited to 'src/backend/utils/init')
-rw-r--r-- | src/backend/utils/init/Makefile | 20 | ||||
-rw-r--r-- | src/backend/utils/init/globals.c | 152 | ||||
-rw-r--r-- | src/backend/utils/init/miscinit.c | 1700 | ||||
-rw-r--r-- | src/backend/utils/init/postinit.c | 1278 |
4 files changed, 3150 insertions, 0 deletions
diff --git a/src/backend/utils/init/Makefile b/src/backend/utils/init/Makefile new file mode 100644 index 0000000..3625693 --- /dev/null +++ b/src/backend/utils/init/Makefile @@ -0,0 +1,20 @@ +#------------------------------------------------------------------------- +# +# Makefile-- +# Makefile for utils/init +# +# IDENTIFICATION +# src/backend/utils/init/Makefile +# +#------------------------------------------------------------------------- + +subdir = src/backend/utils/init +top_builddir = ../../../.. +include $(top_builddir)/src/Makefile.global + +OBJS = \ + globals.o \ + miscinit.o \ + postinit.o + +include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c new file mode 100644 index 0000000..381d9e5 --- /dev/null +++ b/src/backend/utils/init/globals.c @@ -0,0 +1,152 @@ +/*------------------------------------------------------------------------- + * + * globals.c + * global variable declarations + * + * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/init/globals.c + * + * NOTES + * Globals used all over the place should be declared here and not + * in other modules. + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "common/file_perm.h" +#include "libpq/libpq-be.h" +#include "libpq/pqcomm.h" +#include "miscadmin.h" +#include "storage/backendid.h" + + +ProtocolVersion FrontendProtocol; + +volatile sig_atomic_t InterruptPending = false; +volatile sig_atomic_t QueryCancelPending = false; +volatile sig_atomic_t ProcDiePending = false; +volatile sig_atomic_t CheckClientConnectionPending = false; +volatile sig_atomic_t ClientConnectionLost = false; +volatile sig_atomic_t IdleInTransactionSessionTimeoutPending = false; +volatile sig_atomic_t IdleSessionTimeoutPending = false; +volatile sig_atomic_t ProcSignalBarrierPending = false; +volatile sig_atomic_t LogMemoryContextPending = false; +volatile uint32 InterruptHoldoffCount = 0; +volatile uint32 QueryCancelHoldoffCount = 0; +volatile uint32 CritSectionCount = 0; + +int MyProcPid; +pg_time_t MyStartTime; +TimestampTz MyStartTimestamp; +struct Port *MyProcPort; +int32 MyCancelKey; +int MyPMChildSlot; + +/* + * MyLatch points to the latch that should be used for signal handling by the + * current process. It will either point to a process local latch if the + * current process does not have a PGPROC entry in that moment, or to + * PGPROC->procLatch if it has. Thus it can always be used in signal handlers, + * without checking for its existence. + */ +struct Latch *MyLatch; + +/* + * DataDir is the absolute path to the top level of the PGDATA directory tree. + * Except during early startup, this is also the server's working directory; + * most code therefore can simply use relative paths and not reference DataDir + * explicitly. + */ +char *DataDir = NULL; + +/* + * Mode of the data directory. The default is 0700 but it may be changed in + * checkDataDir() to 0750 if the data directory actually has that mode. + */ +int data_directory_mode = PG_DIR_MODE_OWNER; + +char OutputFileName[MAXPGPATH]; /* debugging output file */ + +char my_exec_path[MAXPGPATH]; /* full path to my executable */ +char pkglib_path[MAXPGPATH]; /* full path to lib directory */ + +#ifdef EXEC_BACKEND +char postgres_exec_path[MAXPGPATH]; /* full path to backend */ + +/* note: currently this is not valid in backend processes */ +#endif + +BackendId MyBackendId = InvalidBackendId; + +BackendId ParallelLeaderBackendId = InvalidBackendId; + +Oid MyDatabaseId = InvalidOid; + +Oid MyDatabaseTableSpace = InvalidOid; + +/* + * DatabasePath is the path (relative to DataDir) of my database's + * primary directory, ie, its directory in the default tablespace. + */ +char *DatabasePath = NULL; + +pid_t PostmasterPid = 0; + +/* + * IsPostmasterEnvironment is true in a postmaster process and any postmaster + * child process; it is false in a standalone process (bootstrap or + * standalone backend). IsUnderPostmaster is true in postmaster child + * processes. Note that "child process" includes all children, not only + * regular backends. These should be set correctly as early as possible + * in the execution of a process, so that error handling will do the right + * things if an error should occur during process initialization. + * + * These are initialized for the bootstrap/standalone case. + */ +bool IsPostmasterEnvironment = false; +bool IsUnderPostmaster = false; +bool IsBinaryUpgrade = false; +bool IsBackgroundWorker = false; + +bool ExitOnAnyError = false; + +int DateStyle = USE_ISO_DATES; +int DateOrder = DATEORDER_MDY; +int IntervalStyle = INTSTYLE_POSTGRES; + +bool enableFsync = true; +bool allowSystemTableMods = false; +int work_mem = 4096; +double hash_mem_multiplier = 1.0; +int maintenance_work_mem = 65536; +int max_parallel_maintenance_workers = 2; + +/* + * Primary determinants of sizes of shared-memory structures. + * + * MaxBackends is computed by PostmasterMain after modules have had a chance to + * register background workers. + */ +int NBuffers = 1000; +int MaxConnections = 90; +int max_worker_processes = 8; +int max_parallel_workers = 8; +int MaxBackends = 0; + +int VacuumCostPageHit = 1; /* GUC parameters for vacuum */ +int VacuumCostPageMiss = 2; +int VacuumCostPageDirty = 20; +int VacuumCostLimit = 200; +double VacuumCostDelay = 0; + +int64 VacuumPageHit = 0; +int64 VacuumPageMiss = 0; +int64 VacuumPageDirty = 0; + +int VacuumCostBalance = 0; /* working state for vacuum */ +bool VacuumCostActive = false; diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c new file mode 100644 index 0000000..922f553 --- /dev/null +++ b/src/backend/utils/init/miscinit.c @@ -0,0 +1,1700 @@ +/*------------------------------------------------------------------------- + * + * miscinit.c + * miscellaneous initialization support stuff + * + * Portions Copyright (c) 1996-2021, 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 <sys/param.h> +#include <signal.h> +#include <time.h> +#include <sys/file.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <fcntl.h> +#include <unistd.h> +#include <grp.h> +#include <pwd.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <utime.h> + +#include "access/htup_details.h" +#include "catalog/pg_authid.h" +#include "common/file_perm.h" +#include "libpq/libpq.h" +#include "libpq/pqsignal.h" +#include "mb/pg_wchar.h" +#include "miscadmin.h" +#include "pgstat.h" +#include "postmaster/autovacuum.h" +#include "postmaster/interrupt.h" +#include "postmaster/postmaster.h" +#include "storage/fd.h" +#include "storage/ipc.h" +#include "storage/latch.h" +#include "storage/pg_shmem.h" +#include "storage/pmsignal.h" +#include "storage/proc.h" +#include "storage/procarray.h" +#include "utils/builtins.h" +#include "utils/guc.h" +#include "utils/inval.h" +#include "utils/memutils.h" +#include "utils/pidfile.h" +#include "utils/syscache.h" +#include "utils/varlena.h" + + +#define DIRECTORY_LOCK_FILE "postmaster.pid" + +ProcessingMode Mode = InitProcessing; + +BackendType MyBackendType; + +/* List of lock files to be removed at proc exit */ +static List *lock_files = NIL; + +static Latch LocalLatchData; + +/* ---------------------------------------------------------------- + * 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. + * ---------------------------------------------------------------- + */ + +bool IgnoreSystemIndexes = false; + + +/* ---------------------------------------------------------------- + * common process startup code + * ---------------------------------------------------------------- + */ + +/* + * Initialize the basic environment for a postmaster child + * + * Should be called as early as possible after the child's startup. + */ +void +InitPostmasterChild(void) +{ + IsUnderPostmaster = true; /* we are a postmaster subprocess now */ + + /* + * Set reference point for stack-depth checking. This might seem + * redundant in !EXEC_BACKEND builds; but it's not because the postmaster + * launches its children from signal handlers, so we might be running on + * an alternative stack. + */ + (void) set_stack_base(); + + InitProcessGlobals(); + + /* + * make sure stderr is in binary mode before anything can possibly be + * written to it, in case it's actually the syslogger pipe, so the pipe + * chunking protocol isn't disturbed. Non-logpipe data gets translated on + * redirection (e.g. via pg_ctl -l) anyway. + */ +#ifdef WIN32 + _setmode(fileno(stderr), _O_BINARY); +#endif + + /* We don't want the postmaster's proc_exit() handlers */ + on_exit_reset(); + + /* In EXEC_BACKEND case we will not have inherited BlockSig etc values */ +#ifdef EXEC_BACKEND + pqinitmask(); +#endif + + /* Initialize process-local latch support */ + InitializeLatchSupport(); + MyLatch = &LocalLatchData; + InitLatch(MyLatch); + InitializeLatchWaitSet(); + + /* + * If possible, make this process a group leader, so that the postmaster + * can signal any child processes too. Not all processes will have + * children, but for consistency we make all postmaster child processes do + * this. + */ +#ifdef HAVE_SETSID + if (setsid() < 0) + elog(FATAL, "setsid() failed: %m"); +#endif + + /* + * Every postmaster child process is expected to respond promptly to + * SIGQUIT at all times. Therefore we centrally remove SIGQUIT from + * BlockSig and install a suitable signal handler. (Client-facing + * processes may choose to replace this default choice of handler with + * quickdie().) All other blockable signals remain blocked for now. + */ + pqsignal(SIGQUIT, SignalHandlerForCrashExit); + + sigdelset(&BlockSig, SIGQUIT); + PG_SETMASK(&BlockSig); + + /* Request a signal if the postmaster dies, if possible. */ + PostmasterDeathSignalInit(); +} + +/* + * Initialize the basic environment for a standalone process. + * + * argv0 has to be suitable to find the program's executable. + */ +void +InitStandaloneProcess(const char *argv0) +{ + Assert(!IsPostmasterEnvironment); + + InitProcessGlobals(); + + /* Initialize process-local latch support */ + InitializeLatchSupport(); + MyLatch = &LocalLatchData; + InitLatch(MyLatch); + InitializeLatchWaitSet(); + + /* + * For consistency with InitPostmasterChild, initialize signal mask here. + * But we don't unblock SIGQUIT or provide a default handler for it. + */ + pqinitmask(); + PG_SETMASK(&BlockSig); + + /* Compute paths, no postmaster to inherit from */ + if (my_exec_path[0] == '\0') + { + if (find_my_exec(argv0, my_exec_path) < 0) + elog(FATAL, "%s: could not locate my own executable path", + argv0); + } + + if (pkglib_path[0] == '\0') + get_pkglib_path(my_exec_path, pkglib_path); +} + +void +SwitchToSharedLatch(void) +{ + Assert(MyLatch == &LocalLatchData); + Assert(MyProc != NULL); + + MyLatch = &MyProc->procLatch; + + if (FeBeWaitSet) + ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetLatchPos, WL_LATCH_SET, + MyLatch); + + /* + * Set the shared latch as the local one might have been set. This + * shouldn't normally be necessary as code is supposed to check the + * condition before waiting for the latch, but a bit care can't hurt. + */ + SetLatch(MyLatch); +} + +void +SwitchBackToLocalLatch(void) +{ + Assert(MyLatch != &LocalLatchData); + Assert(MyProc != NULL && MyLatch == &MyProc->procLatch); + + MyLatch = &LocalLatchData; + + if (FeBeWaitSet) + ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetLatchPos, WL_LATCH_SET, + MyLatch); + + SetLatch(MyLatch); +} + +const char * +GetBackendTypeDesc(BackendType backendType) +{ + const char *backendDesc = "unknown process type"; + + switch (backendType) + { + case B_INVALID: + backendDesc = "not initialized"; + break; + case B_AUTOVAC_LAUNCHER: + backendDesc = "autovacuum launcher"; + break; + case B_AUTOVAC_WORKER: + backendDesc = "autovacuum worker"; + break; + case B_BACKEND: + backendDesc = "client backend"; + break; + case B_BG_WORKER: + backendDesc = "background worker"; + break; + case B_BG_WRITER: + backendDesc = "background writer"; + break; + case B_CHECKPOINTER: + backendDesc = "checkpointer"; + break; + case B_STARTUP: + backendDesc = "startup"; + break; + case B_WAL_RECEIVER: + backendDesc = "walreceiver"; + break; + case B_WAL_SENDER: + backendDesc = "walsender"; + break; + case B_WAL_WRITER: + backendDesc = "walwriter"; + break; + case B_ARCHIVER: + backendDesc = "archiver"; + break; + case B_STATS_COLLECTOR: + backendDesc = "stats collector"; + break; + case B_LOGGER: + backendDesc = "logger"; + break; + } + + return backendDesc; +} + +/* ---------------------------------------------------------------- + * database path / name support stuff + * ---------------------------------------------------------------- + */ + +void +SetDatabasePath(const char *path) +{ + /* This should happen only once per process */ + Assert(!DatabasePath); + DatabasePath = MemoryContextStrdup(TopMemoryContext, path); +} + +/* + * Validate the proposed data directory. + * + * Also initialize file and directory create modes and mode mask. + */ +void +checkDataDir(void) +{ + struct stat stat_buf; + + Assert(DataDir); + + if (stat(DataDir, &stat_buf) != 0) + { + if (errno == ENOENT) + ereport(FATAL, + (errcode_for_file_access(), + errmsg("data directory \"%s\" does not exist", + DataDir))); + else + ereport(FATAL, + (errcode_for_file_access(), + errmsg("could not read permissions of directory \"%s\": %m", + DataDir))); + } + + /* eventual chdir would fail anyway, but let's test ... */ + if (!S_ISDIR(stat_buf.st_mode)) + ereport(FATAL, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("specified data directory \"%s\" is not a directory", + DataDir))); + + /* + * Check that the directory belongs to my userid; if not, reject. + * + * This check is an essential part of the interlock that prevents two + * postmasters from starting in the same directory (see CreateLockFile()). + * Do not remove or weaken it. + * + * XXX can we safely enable this check on Windows? + */ +#if !defined(WIN32) && !defined(__CYGWIN__) + if (stat_buf.st_uid != geteuid()) + ereport(FATAL, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("data directory \"%s\" has wrong ownership", + DataDir), + errhint("The server must be started by the user that owns the data directory."))); +#endif + + /* + * Check if the directory has correct permissions. If not, reject. + * + * Only two possible modes are allowed, 0700 and 0750. The latter mode + * indicates that group read/execute should be allowed on all newly + * created files and directories. + * + * XXX temporarily suppress check when on Windows, because there may not + * be proper support for Unix-y file permissions. Need to think of a + * reasonable check to apply on Windows. + */ +#if !defined(WIN32) && !defined(__CYGWIN__) + if (stat_buf.st_mode & PG_MODE_MASK_GROUP) + ereport(FATAL, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("data directory \"%s\" has invalid permissions", + DataDir), + errdetail("Permissions should be u=rwx (0700) or u=rwx,g=rx (0750)."))); +#endif + + /* + * Reset creation modes and mask based on the mode of the data directory. + * + * The mask was set earlier in startup to disallow group permissions on + * newly created files and directories. However, if group read/execute + * are present on the data directory then modify the create modes and mask + * to allow group read/execute on newly created files and directories and + * set the data_directory_mode GUC. + * + * Suppress when on Windows, because there may not be proper support for + * Unix-y file permissions. + */ +#if !defined(WIN32) && !defined(__CYGWIN__) + SetDataDirectoryCreatePerm(stat_buf.st_mode); + + umask(pg_mode_mask); + data_directory_mode = pg_dir_create_mode; +#endif + + /* Check for PG_VERSION */ + ValidatePgVersion(DataDir); +} + +/* + * Set data directory, but make sure it's an absolute path. Use this, + * never set DataDir directly. + */ +void +SetDataDir(const char *dir) +{ + char *new; + + AssertArg(dir); + + /* If presented path is relative, convert to absolute */ + new = make_absolute_path(dir); + + if (DataDir) + free(DataDir); + DataDir = new; +} + +/* + * Change working directory to DataDir. Most of the postmaster and backend + * code assumes that we are in 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(DataDir); + + if (chdir(DataDir) < 0) + ereport(FATAL, + (errcode_for_file_access(), + errmsg("could not change directory to \"%s\": %m", + DataDir))); +} + + +/* ---------------------------------------------------------------- + * User ID state + * + * We have to track several different values associated with the concept + * of "user ID". + * + * AuthenticatedUserId is determined at connection start and never changes. + * + * SessionUserId is initially the same as AuthenticatedUserId, but can be + * changed by SET SESSION AUTHORIZATION (if AuthenticatedUserIsSuperuser). + * This is the ID reported by the SESSION_USER SQL function. + * + * 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. (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; +static Oid SessionUserId = InvalidOid; +static Oid OuterUserId = InvalidOid; +static Oid CurrentUserId = InvalidOid; + +/* We also have to remember the superuser state of some of these levels */ +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 - get the current effective user ID. + * + * Note: there's no SetUserId() anymore; use SetUserIdAndSecContext(). + */ +Oid +GetUserId(void) +{ + AssertState(OidIsValid(CurrentUserId)); + return CurrentUserId; +} + + +/* + * GetOuterUserId/SetOuterUserId - get/set the outer-level user ID. + */ +Oid +GetOuterUserId(void) +{ + AssertState(OidIsValid(OuterUserId)); + return OuterUserId; +} + + +static void +SetOuterUserId(Oid userid) +{ + AssertState(SecurityRestrictionContext == 0); + AssertArg(OidIsValid(userid)); + OuterUserId = userid; + + /* We force the effective user ID to match, too */ + CurrentUserId = userid; +} + + +/* + * GetSessionUserId/SetSessionUserId - get/set the session user ID. + */ +Oid +GetSessionUserId(void) +{ + AssertState(OidIsValid(SessionUserId)); + return SessionUserId; +} + + +static void +SetSessionUserId(Oid userid, bool is_superuser) +{ + AssertState(SecurityRestrictionContext == 0); + AssertArg(OidIsValid(userid)); + SessionUserId = userid; + SessionUserIsSuperuser = is_superuser; + SetRoleIsActive = false; + + /* We force the effective user IDs to match, too */ + OuterUserId = userid; + CurrentUserId = userid; +} + +/* + * GetAuthenticatedUserId - get the authenticated user ID + */ +Oid +GetAuthenticatedUserId(void) +{ + AssertState(OidIsValid(AuthenticatedUserId)); + return AuthenticatedUserId; +} + + +/* + * GetUserIdAndSecContext/SetUserIdAndSecContext - get/set the current user ID + * and the SecurityRestrictionContext flags. + * + * Currently there are three 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. The + * policy is to use this before operations, e.g. autovacuum and REINDEX, that + * enumerate relations of a database or schema and run functions associated + * with each found relation. The relation owner is the new user ID. Set this + * as soon as possible after locking the relation. Restore the old user ID as + * late as possible before closing the relation; restoring it shortly after + * close is also tolerable. If a command has both relation-enumerating and + * non-enumerating modes, e.g. ANALYZE, both modes set this bit. 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. These restrictions are fairly + * draconian, but the functions called in relation-enumerating operations are + * really supposed to be side-effect-free anyway. + * + * SECURITY_NOFORCE_RLS indicates that we are inside an operation which should + * ignore the FORCE ROW LEVEL SECURITY per-table indication. This is used to + * ensure that FORCE RLS does not mistakenly break referential integrity + * checks. Note that this is intentionally only checked when running as the + * owner of the table (which should always be the case for referential + * integrity checks). + * + * 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; +} + +/* + * InNoForceRLSOperation - are we ignoring FORCE ROW LEVEL SECURITY ? + */ +bool +InNoForceRLSOperation(void) +{ + return (SecurityRestrictionContext & SECURITY_NOFORCE_RLS) != 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 whether specified role has explicit REPLICATION privilege + */ +bool +has_rolreplication(Oid roleid) +{ + bool result = false; + HeapTuple utup; + + utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid)); + if (HeapTupleIsValid(utup)) + { + result = ((Form_pg_authid) GETSTRUCT(utup))->rolreplication; + ReleaseSysCache(utup); + } + return result; +} + +/* + * Initialize user identity during normal backend startup + */ +void +InitializeSessionUserId(const char *rolename, Oid roleid) +{ + HeapTuple roleTup; + Form_pg_authid rform; + char *rname; + + /* + * 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()); + + /* call only once */ + AssertState(!OidIsValid(AuthenticatedUserId)); + + /* + * Make sure syscache entries are flushed for recent catalog changes. This + * allows us to find roles that were created on-the-fly during + * authentication. + */ + AcceptInvalidationMessages(); + + if (rolename != NULL) + { + roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(rolename)); + if (!HeapTupleIsValid(roleTup)) + ereport(FATAL, + (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), + errmsg("role \"%s\" does not exist", rolename))); + } + else + { + roleTup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid)); + if (!HeapTupleIsValid(roleTup)) + ereport(FATAL, + (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), + errmsg("role with OID %u does not exist", roleid))); + } + + rform = (Form_pg_authid) GETSTRUCT(roleTup); + roleid = rform->oid; + rname = NameStr(rform->rolname); + + AuthenticatedUserId = roleid; + AuthenticatedUserIsSuperuser = rform->rolsuper; + + /* This sets OuterUserId/CurrentUserId too */ + SetSessionUserId(roleid, AuthenticatedUserIsSuperuser); + + /* Also mark our PGPROC entry with the authenticated user id */ + /* (We assume this is an atomic store so no lock is needed) */ + MyProc->roleId = roleid; + + /* + * 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 log in", + rname))); + + /* + * 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 && + !AuthenticatedUserIsSuperuser && + CountUserBackends(roleid) > rform->rolconnlimit) + ereport(FATAL, + (errcode(ERRCODE_TOO_MANY_CONNECTIONS), + errmsg("too many connections for role \"%s\"", + rname))); + } + + /* Record username and superuser status as GUC settings too */ + SetConfigOption("session_authorization", rname, + PGC_BACKEND, PGC_S_OVERRIDE); + SetConfigOption("is_superuser", + AuthenticatedUserIsSuperuser ? "on" : "off", + PGC_INTERNAL, PGC_S_OVERRIDE); + + ReleaseSysCache(roleTup); +} + + +/* + * Initialize user identity during special backend startup + */ +void +InitializeSessionUserIdStandalone(void) +{ + /* + * This function should only be called in single-user mode, in autovacuum + * workers, and in background workers. + */ + AssertState(!IsUnderPostmaster || IsAutoVacuumWorkerProcess() || IsBackgroundWorker); + + /* call only once */ + AssertState(!OidIsValid(AuthenticatedUserId)); + + AuthenticatedUserId = BOOTSTRAP_SUPERUSERID; + AuthenticatedUserIsSuperuser = true; + + SetSessionUserId(BOOTSTRAP_SUPERUSERID, true); +} + + +/* + * 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 userid, bool is_superuser) +{ + /* Must have authenticated already, else can't make permission check */ + AssertState(OidIsValid(AuthenticatedUserId)); + + if (userid != AuthenticatedUserId && + !AuthenticatedUserIsSuperuser) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to set session authorization"))); + + SetSessionUserId(userid, is_superuser); + + SetConfigOption("is_superuser", + 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 (SetRoleIsActive) + return 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 roleid, bool is_superuser) +{ + /* + * Get correct info if it's SET ROLE NONE + * + * If 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(roleid)) + { + if (!OidIsValid(SessionUserId)) + return; + + roleid = SessionUserId; + is_superuser = SessionUserIsSuperuser; + + SetRoleIsActive = false; + } + else + SetRoleIsActive = true; + + SetOuterUserId(roleid); + + SetConfigOption("is_superuser", + is_superuser ? "on" : "off", + PGC_INTERNAL, PGC_S_OVERRIDE); +} + + +/* + * Get user name from user oid, returns NULL for nonexistent roleid if noerr + * is true. + */ +char * +GetUserNameFromId(Oid roleid, bool noerr) +{ + HeapTuple tuple; + char *result; + + tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid)); + if (!HeapTupleIsValid(tuple)) + { + if (!noerr) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("invalid role OID: %u", roleid))); + result = NULL; + } + else + { + 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 Unix-socket-file lockfiles ($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 pidfile.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 lockfiles. + */ +static void +UnlinkLockFiles(int status, Datum arg) +{ + ListCell *l; + + foreach(l, lock_files) + { + char *curfile = (char *) lfirst(l); + + unlink(curfile); + /* Should we complain if the unlink fails? */ + } + /* Since we're about to exit, no need to reclaim storage */ + lock_files = NIL; + + /* + * Lock file removal should always be the last externally visible action + * of a postmaster or standalone backend, while we won't come here at all + * when exiting postmaster child processes. Therefore, this is a good + * place to log completion of shutdown. We could alternatively teach + * proc_exit() to do it, but that seems uglier. In a standalone backend, + * use NOTICE elevel to be less chatty. + */ + ereport(IsPostmasterEnvironment ? LOG : NOTICE, + (errmsg("database system is shut down"))); +} + +/* + * Create a lockfile. + * + * filename is the path name of the lockfile to create. + * amPostmaster is used to determine how to encode the output PID. + * socketDir is the Unix socket directory path to include (possibly empty). + * isDDLock and refName are used to determine what error message to produce. + */ +static void +CreateLockFile(const char *filename, bool amPostmaster, + const char *socketDir, + bool isDDLock, const char *refName) +{ + int fd; + char buffer[MAXPGPATH * 2 + 256]; + int ntries; + int len; + int encoded_pid; + pid_t other_pid; + 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 + * (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/0640. See + * comments below. + */ + fd = open(filename, O_RDWR | O_CREAT | O_EXCL, pg_file_create_mode); + 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", + filename))); + + /* + * 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(filename, O_RDONLY, pg_file_create_mode); + 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", + filename))); + } + pgstat_report_wait_start(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", + filename))); + pgstat_report_wait_end(); + close(fd); + + if (len == 0) + { + ereport(FATAL, + (errcode(ERRCODE_LOCK_FILE_EXISTS), + errmsg("lock file \"%s\" is empty", filename), + errhint("Either another server is starting, or the lock file is the remnant of a previous server startup crash."))); + } + + 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) + elog(FATAL, "bogus data in lock file \"%s\": \"%s\"", + filename, 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, as enforced in checkDataDir(). Also, since we + * create the lockfiles mode 0600/0640, 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 */ + ereport(FATAL, + (errcode(ERRCODE_LOCK_FILE_EXISTS), + errmsg("lock file \"%s\" already exists", + filename), + isDDLock ? + (encoded_pid < 0 ? + errhint("Is another postgres (PID %d) running in data directory \"%s\"?", + (int) other_pid, refName) : + errhint("Is another postmaster (PID %d) running in data directory \"%s\"?", + (int) other_pid, refName)) : + (encoded_pid < 0 ? + errhint("Is another postgres (PID %d) using socket file \"%s\"?", + (int) other_pid, refName) : + errhint("Is another postmaster (PID %d) using socket file \"%s\"?", + (int) other_pid, refName)))); + } + } + + /* + * 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 (isDDLock) + { + char *ptr = buffer; + unsigned long id1, + id2; + int lineno; + + for (lineno = 1; lineno < LOCK_FILE_LINE_SHMEM_KEY; lineno++) + { + if ((ptr = strchr(ptr, '\n')) == NULL) + break; + ptr++; + } + + 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("Terminate any old server processes associated with data directory \"%s\".", + refName))); + } + } + + /* + * 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(filename) < 0) + ereport(FATAL, + (errcode_for_file_access(), + errmsg("could not remove old lock file \"%s\": %m", + filename), + errhint("The file seems accidentally left over, but " + "it could not be removed. Please remove the file " + "by hand and try again."))); + } + + /* + * Successfully created the file, now fill it. See comment in pidfile.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. + */ + snprintf(buffer, sizeof(buffer), "%d\n%s\n%ld\n%d\n%s\n", + amPostmaster ? (int) my_pid : -((int) my_pid), + DataDir, + (long) MyStartTime, + PostPortNumber, + socketDir); + + /* + * In a standalone backend, the next line (LOCK_FILE_LINE_LISTEN_ADDR) + * will never receive data, so fill it in as empty now. + */ + if (isDDLock && !amPostmaster) + strlcat(buffer, "\n", sizeof(buffer)); + + errno = 0; + pgstat_report_wait_start(WAIT_EVENT_LOCK_FILE_CREATE_WRITE); + if (write(fd, buffer, strlen(buffer)) != strlen(buffer)) + { + int save_errno = errno; + + close(fd); + unlink(filename); + /* 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", filename))); + } + pgstat_report_wait_end(); + + pgstat_report_wait_start(WAIT_EVENT_LOCK_FILE_CREATE_SYNC); + 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))); + } + pgstat_report_wait_end(); + if (close(fd) != 0) + { + int save_errno = errno; + + unlink(filename); + errno = save_errno; + ereport(FATAL, + (errcode_for_file_access(), + errmsg("could not write lock file \"%s\": %m", filename))); + } + + /* + * Arrange to unlink the lock file(s) at proc_exit. If this is the first + * one, set up the on_proc_exit function to do it; then add this lock file + * to the list of files to unlink. + */ + if (lock_files == NIL) + on_proc_exit(UnlinkLockFiles, 0); + + /* + * Use lcons so that the lock files are unlinked in reverse order of + * creation; this is critical! + */ + lock_files = lcons(pstrdup(filename), lock_files); +} + +/* + * Create the data directory lockfile. + * + * When this is called, we must have already switched the working + * directory to DataDir, so we can just use a relative path. This + * helps ensure that we are locking the directory we should be. + * + * Note that the socket directory path line is initially written as empty. + * postmaster.c will rewrite it upon creating the first Unix socket. + */ +void +CreateDataDirLockFile(bool amPostmaster) +{ + CreateLockFile(DIRECTORY_LOCK_FILE, amPostmaster, "", true, DataDir); +} + +/* + * Create a lockfile for the specified Unix socket file. + */ +void +CreateSocketLockFile(const char *socketfile, bool amPostmaster, + const char *socketDir) +{ + char lockfile[MAXPGPATH]; + + snprintf(lockfile, sizeof(lockfile), "%s.lock", socketfile); + CreateLockFile(lockfile, amPostmaster, socketDir, false, socketfile); +} + +/* + * TouchSocketLockFiles -- mark socket lock files as recently accessed + * + * This routine should be called every so often to ensure that the socket + * lock files have a recent mod or access date. That saves them + * from being removed by overenthusiastic /tmp-directory-cleaner daemons. + * (Another reason we should never have put the socket file in /tmp...) + */ +void +TouchSocketLockFiles(void) +{ + ListCell *l; + + foreach(l, lock_files) + { + char *socketLockFile = (char *) lfirst(l); + + /* No need to touch the data directory lock file, we trust */ + if (strcmp(socketLockFile, DIRECTORY_LOCK_FILE) == 0) + continue; + + /* we just ignore any error here */ + (void) utime(socketLockFile, NULL); + } +} + + +/* + * Add (or replace) a line in the data directory lock file. + * The given string should not include a trailing newline. + * + * Note: because we don't truncate the file, if we were to rewrite a line + * with less data than it had before, there would be garbage after the last + * line. While we could fix that by adding a truncate call, that would make + * the file update non-atomic, which we'd rather avoid. Therefore, callers + * should endeavor never to shorten a line once it's been written. + */ +void +AddToDataDirLockFile(int target_line, const char *str) +{ + int fd; + int len; + int lineno; + char *srcptr; + char *destptr; + char srcbuffer[BLCKSZ]; + char destbuffer[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_wait_start(WAIT_EVENT_LOCK_FILE_ADDTODATADIR_READ); + len = read(fd, srcbuffer, sizeof(srcbuffer) - 1); + pgstat_report_wait_end(); + if (len < 0) + { + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not read from file \"%s\": %m", + DIRECTORY_LOCK_FILE))); + close(fd); + return; + } + srcbuffer[len] = '\0'; + + /* + * Advance over lines we are not supposed to rewrite, then copy them to + * destbuffer. + */ + srcptr = srcbuffer; + for (lineno = 1; lineno < target_line; lineno++) + { + char *eol = strchr(srcptr, '\n'); + + if (eol == NULL) + break; /* not enough lines in file yet */ + srcptr = eol + 1; + } + memcpy(destbuffer, srcbuffer, srcptr - srcbuffer); + destptr = destbuffer + (srcptr - srcbuffer); + + /* + * Fill in any missing lines before the target line, in case lines are + * added to the file out of order. + */ + for (; lineno < target_line; lineno++) + { + if (destptr < destbuffer + sizeof(destbuffer)) + *destptr++ = '\n'; + } + + /* + * Write or rewrite the target line. + */ + snprintf(destptr, destbuffer + sizeof(destbuffer) - destptr, "%s\n", str); + destptr += strlen(destptr); + + /* + * If there are more lines in the old file, append them to destbuffer. + */ + if ((srcptr = strchr(srcptr, '\n')) != NULL) + { + srcptr++; + snprintf(destptr, destbuffer + sizeof(destbuffer) - destptr, "%s", + srcptr); + } + + /* + * And rewrite the data. Since we write in a single kernel call, this + * update should appear atomic to onlookers. + */ + len = strlen(destbuffer); + errno = 0; + pgstat_report_wait_start(WAIT_EVENT_LOCK_FILE_ADDTODATADIR_WRITE); + if (pg_pwrite(fd, destbuffer, len, 0) != len) + { + pgstat_report_wait_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_wait_end(); + pgstat_report_wait_start(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_wait_end(); + if (close(fd) != 0) + { + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not write to file \"%s\": %m", + DIRECTORY_LOCK_FILE))); + } +} + + +/* + * Recheck that the data directory lock file still exists with expected + * content. Return true if the lock file appears OK, false if it isn't. + * + * We call this periodically in the postmaster. The idea is that if the + * lock file has been removed or replaced by another postmaster, we should + * do a panic database shutdown. Therefore, we should return true if there + * is any doubt: we do not want to cause a panic shutdown unnecessarily. + * Transient failures like EINTR or ENFILE should not cause us to fail. + * (If there really is something wrong, we'll detect it on a future recheck.) + */ +bool +RecheckDataDirLockFile(void) +{ + int fd; + int len; + long file_pid; + char buffer[BLCKSZ]; + + fd = open(DIRECTORY_LOCK_FILE, O_RDWR | PG_BINARY, 0); + if (fd < 0) + { + /* + * There are many foreseeable false-positive error conditions. For + * safety, fail only on enumerated clearly-something-is-wrong + * conditions. + */ + switch (errno) + { + case ENOENT: + case ENOTDIR: + /* disaster */ + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not open file \"%s\": %m", + DIRECTORY_LOCK_FILE))); + return false; + default: + /* non-fatal, at least for now */ + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not open file \"%s\": %m; continuing anyway", + DIRECTORY_LOCK_FILE))); + return true; + } + } + pgstat_report_wait_start(WAIT_EVENT_LOCK_FILE_RECHECKDATADIR_READ); + len = read(fd, buffer, sizeof(buffer) - 1); + pgstat_report_wait_end(); + if (len < 0) + { + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not read from file \"%s\": %m", + DIRECTORY_LOCK_FILE))); + close(fd); + return true; /* treat read failure as nonfatal */ + } + buffer[len] = '\0'; + close(fd); + file_pid = atol(buffer); + if (file_pid == getpid()) + return true; /* all is well */ + + /* Trouble: someone's overwritten the lock file */ + ereport(LOG, + (errmsg("lock file \"%s\" contains wrong PID: %ld instead of %ld", + DIRECTORY_LOCK_FILE, file_pid, (long) getpid()))); + return false; +} + + +/*------------------------------------------------------------------------- + * 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; + int ret; + long file_major; + long my_major; + char *endptr; + char file_version_string[64]; + const char *my_version_string = PG_VERSION; + + my_major = strtol(my_version_string, &endptr, 10); + + snprintf(full_path, sizeof(full_path), "%s/PG_VERSION", path); + + file = AllocateFile(full_path, "r"); + if (!file) + { + 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))); + } + + file_version_string[0] = '\0'; + ret = fscanf(file, "%63s", file_version_string); + file_major = strtol(file_version_string, &endptr, 10); + + if (ret != 1 || endptr == file_version_string) + 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."))); + + FreeFile(file); + + if (my_major != file_major) + ereport(FATAL, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("database files are incompatible with server"), + errdetail("The data directory was initialized by PostgreSQL version %s, " + "which is not compatible with this version %s.", + file_version_string, my_version_string))); +} + +/*------------------------------------------------------------------------- + * Library preload support + *------------------------------------------------------------------------- + */ + +/* + * GUC variables: lists of library names to be preloaded at postmaster + * start and at backend start + */ +char *session_preload_libraries_string = NULL; +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' + * + * '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 *gucname, bool restricted) +{ + char *rawstring; + List *elemlist; + ListCell *l; + + if (libraries == NULL || libraries[0] == '\0') + return; /* nothing to do */ + + /* Need a modifiable copy of string */ + rawstring = pstrdup(libraries); + + /* Parse string into list of filename paths */ + if (!SplitDirectoriesString(rawstring, ',', &elemlist)) + { + /* syntax error in list */ + list_free_deep(elemlist); + pfree(rawstring); + ereport(LOG, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid list syntax in parameter \"%s\"", + gucname))); + return; + } + + foreach(l, elemlist) + { + /* Note that filename was already canonicalized */ + char *filename = (char *) lfirst(l); + char *expanded = NULL; + + /* If restricting, insert $libdir/plugins if not mentioned already */ + if (restricted && first_dir_separator(filename) == NULL) + { + expanded = psprintf("$libdir/plugins/%s", filename); + filename = expanded; + } + load_file(filename, restricted); + ereport(DEBUG1, + (errmsg_internal("loaded library \"%s\"", filename))); + if (expanded) + pfree(expanded); + } + + list_free_deep(elemlist); + pfree(rawstring); +} + +/* + * process any libraries that should be preloaded at postmaster start + */ +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; +} + +/* + * process any libraries that should be preloaded at backend start + */ +void +process_session_preload_libraries(void) +{ + load_libraries(session_preload_libraries_string, + "session_preload_libraries", + false); + load_libraries(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 +} diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c new file mode 100644 index 0000000..51d1bbe --- /dev/null +++ b/src/backend/utils/init/postinit.c @@ -0,0 +1,1278 @@ +/*------------------------------------------------------------------------- + * + * postinit.c + * postgres initialization utilities + * + * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/init/postinit.c + * + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <ctype.h> +#include <fcntl.h> +#include <unistd.h> + +#include "access/genam.h" +#include "access/heapam.h" +#include "access/htup_details.h" +#include "access/session.h" +#include "access/sysattr.h" +#include "access/tableam.h" +#include "access/xact.h" +#include "access/xlog.h" +#include "catalog/catalog.h" +#include "catalog/namespace.h" +#include "catalog/pg_authid.h" +#include "catalog/pg_database.h" +#include "catalog/pg_db_role_setting.h" +#include "catalog/pg_tablespace.h" +#include "libpq/auth.h" +#include "libpq/libpq-be.h" +#include "mb/pg_wchar.h" +#include "miscadmin.h" +#include "pgstat.h" +#include "postmaster/autovacuum.h" +#include "postmaster/postmaster.h" +#include "replication/walsender.h" +#include "storage/bufmgr.h" +#include "storage/fd.h" +#include "storage/ipc.h" +#include "storage/lmgr.h" +#include "storage/proc.h" +#include "storage/procarray.h" +#include "storage/procsignal.h" +#include "storage/sinvaladt.h" +#include "storage/smgr.h" +#include "storage/sync.h" +#include "tcop/tcopprot.h" +#include "utils/acl.h" +#include "utils/fmgroids.h" +#include "utils/guc.h" +#include "utils/memutils.h" +#include "utils/pg_locale.h" +#include "utils/portal.h" +#include "utils/ps_status.h" +#include "utils/snapmgr.h" +#include "utils/syscache.h" +#include "utils/timeout.h" + +static HeapTuple GetDatabaseTuple(const char *dbname); +static HeapTuple GetDatabaseTupleByOid(Oid dboid); +static void PerformAuthentication(Port *port); +static void CheckMyDatabase(const char *name, bool am_superuser, bool override_allow_connections); +static void InitCommunication(void); +static void ShutdownPostgres(int code, Datum arg); +static void StatementTimeoutHandler(void); +static void LockTimeoutHandler(void); +static void IdleInTransactionSessionTimeoutHandler(void); +static void IdleSessionTimeoutHandler(void); +static void ClientCheckTimeoutHandler(void); +static bool ThereIsAtLeastOneRole(void); +static void process_startup_options(Port *port, bool am_superuser); +static void process_settings(Oid databaseid, Oid roleid); + + +/*** InitPostgres support ***/ + + +/* + * GetDatabaseTuple -- fetch the pg_database row for a database + * + * This is used during backend startup when we don't yet have any access to + * system catalogs in general. In the worst case, we can seqscan pg_database + * using nothing but the hard-wired descriptor that relcache.c creates for + * pg_database. In more typical cases, relcache.c was able to load + * descriptors for both pg_database and its indexes from the shared relcache + * cache file, and so we can do an indexscan. criticalSharedRelcachesBuilt + * tells whether we got the cached descriptors. + */ +static HeapTuple +GetDatabaseTuple(const char *dbname) +{ + HeapTuple tuple; + Relation relation; + SysScanDesc scan; + ScanKeyData key[1]; + + /* + * form a scan key + */ + ScanKeyInit(&key[0], + Anum_pg_database_datname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(dbname)); + + /* + * Open pg_database and fetch a tuple. Force heap scan if we haven't yet + * built the critical shared relcache entries (i.e., we're starting up + * without a shared relcache cache file). + */ + relation = table_open(DatabaseRelationId, AccessShareLock); + scan = systable_beginscan(relation, DatabaseNameIndexId, + criticalSharedRelcachesBuilt, + NULL, + 1, key); + + tuple = systable_getnext(scan); + + /* Must copy tuple before releasing buffer */ + if (HeapTupleIsValid(tuple)) + tuple = heap_copytuple(tuple); + + /* all done */ + systable_endscan(scan); + table_close(relation, AccessShareLock); + + return tuple; +} + +/* + * GetDatabaseTupleByOid -- as above, but search by database OID + */ +static HeapTuple +GetDatabaseTupleByOid(Oid dboid) +{ + HeapTuple tuple; + Relation relation; + SysScanDesc scan; + ScanKeyData key[1]; + + /* + * form a scan key + */ + ScanKeyInit(&key[0], + Anum_pg_database_oid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(dboid)); + + /* + * Open pg_database and fetch a tuple. Force heap scan if we haven't yet + * built the critical shared relcache entries (i.e., we're starting up + * without a shared relcache cache file). + */ + relation = table_open(DatabaseRelationId, AccessShareLock); + scan = systable_beginscan(relation, DatabaseOidIndexId, + criticalSharedRelcachesBuilt, + NULL, + 1, key); + + tuple = systable_getnext(scan); + + /* Must copy tuple before releasing buffer */ + if (HeapTupleIsValid(tuple)) + tuple = heap_copytuple(tuple); + + /* all done */ + systable_endscan(scan); + table_close(relation, AccessShareLock); + + return tuple; +} + + +/* + * PerformAuthentication -- authenticate a remote client + * + * returns: nothing. Will not return at all if there's any failure. + */ +static void +PerformAuthentication(Port *port) +{ + /* This should be set already, but let's make sure */ + ClientAuthInProgress = true; /* limit visibility of log messages */ + + /* + * In EXEC_BACKEND case, we didn't inherit the contents of pg_hba.conf + * etcetera from the postmaster, and have to load them ourselves. + * + * FIXME: [fork/exec] Ugh. Is there a way around this overhead? + */ +#ifdef EXEC_BACKEND + + /* + * load_hba() and load_ident() want to work within the PostmasterContext, + * so create that if it doesn't exist (which it won't). We'll delete it + * again later, in PostgresMain. + */ + if (PostmasterContext == NULL) + PostmasterContext = AllocSetContextCreate(TopMemoryContext, + "Postmaster", + ALLOCSET_DEFAULT_SIZES); + + if (!load_hba()) + { + /* + * It makes no sense to continue if we fail to load the HBA file, + * since there is no way to connect to the database in this case. + */ + ereport(FATAL, + (errmsg("could not load pg_hba.conf"))); + } + + if (!load_ident()) + { + /* + * It is ok to continue if we fail to load the IDENT file, although it + * means that you cannot log in using any of the authentication + * methods that need a user name mapping. load_ident() already logged + * the details of error to the log. + */ + } +#endif + + /* + * Set up a timeout in case a buggy or malicious client fails to respond + * during authentication. Since we're inside a transaction and might do + * database access, we have to use the statement_timeout infrastructure. + */ + enable_timeout_after(STATEMENT_TIMEOUT, AuthenticationTimeout * 1000); + + /* + * Now perform authentication exchange. + */ + set_ps_display("authentication"); + ClientAuthentication(port); /* might not return, if failure */ + + /* + * Done with authentication. Disable the timeout, and log if needed. + */ + disable_timeout(STATEMENT_TIMEOUT, false); + + if (Log_connections) + { + StringInfoData logmsg; + + initStringInfo(&logmsg); + if (am_walsender) + appendStringInfo(&logmsg, _("replication connection authorized: user=%s"), + port->user_name); + else + appendStringInfo(&logmsg, _("connection authorized: user=%s"), + port->user_name); + if (!am_walsender) + appendStringInfo(&logmsg, _(" database=%s"), port->database_name); + + if (port->application_name != NULL) + appendStringInfo(&logmsg, _(" application_name=%s"), + port->application_name); + +#ifdef USE_SSL + if (port->ssl_in_use) + appendStringInfo(&logmsg, _(" SSL enabled (protocol=%s, cipher=%s, bits=%d)"), + be_tls_get_version(port), + be_tls_get_cipher(port), + be_tls_get_cipher_bits(port)); +#endif +#ifdef ENABLE_GSS + if (port->gss) + { + const char *princ = be_gssapi_get_princ(port); + + if (princ) + appendStringInfo(&logmsg, + _(" GSS (authenticated=%s, encrypted=%s, principal=%s)"), + be_gssapi_get_auth(port) ? _("yes") : _("no"), + be_gssapi_get_enc(port) ? _("yes") : _("no"), + princ); + else + appendStringInfo(&logmsg, + _(" GSS (authenticated=%s, encrypted=%s)"), + be_gssapi_get_auth(port) ? _("yes") : _("no"), + be_gssapi_get_enc(port) ? _("yes") : _("no")); + } +#endif + + ereport(LOG, errmsg_internal("%s", logmsg.data)); + pfree(logmsg.data); + } + + set_ps_display("startup"); + + ClientAuthInProgress = false; /* client_min_messages is active now */ +} + + +/* + * CheckMyDatabase -- fetch information from the pg_database entry for our DB + */ +static void +CheckMyDatabase(const char *name, bool am_superuser, bool override_allow_connections) +{ + HeapTuple tup; + Form_pg_database dbform; + char *collate; + char *ctype; + + /* Fetch our pg_database row normally, via syscache */ + tup = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(MyDatabaseId)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for database %u", MyDatabaseId); + dbform = (Form_pg_database) GETSTRUCT(tup); + + /* This recheck is strictly paranoia */ + if (strcmp(name, NameStr(dbform->datname)) != 0) + ereport(FATAL, + (errcode(ERRCODE_UNDEFINED_DATABASE), + errmsg("database \"%s\" has disappeared from pg_database", + name), + errdetail("Database OID %u now seems to belong to \"%s\".", + MyDatabaseId, NameStr(dbform->datname)))); + + /* + * Check permissions to connect to the database. + * + * These checks are not enforced when in standalone mode, so that there is + * a way to recover from disabling all access to all databases, for + * example "UPDATE pg_database SET datallowconn = false;". + * + * We do not enforce them for autovacuum worker processes either. + */ + if (IsUnderPostmaster && !IsAutoVacuumWorkerProcess()) + { + /* + * Check that the database is currently allowing connections. + */ + if (!dbform->datallowconn && !override_allow_connections) + ereport(FATAL, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("database \"%s\" is not currently accepting connections", + name))); + + /* + * Check privilege to connect to the database. (The am_superuser test + * is redundant, but since we have the flag, might as well check it + * and save a few cycles.) + */ + if (!am_superuser && + pg_database_aclcheck(MyDatabaseId, GetUserId(), + ACL_CONNECT) != ACLCHECK_OK) + ereport(FATAL, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied for database \"%s\"", name), + errdetail("User does not have CONNECT privilege."))); + + /* + * Check connection limit for this database. + * + * 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 (dbform->datconnlimit >= 0 && + !am_superuser && + CountDBConnections(MyDatabaseId) > dbform->datconnlimit) + ereport(FATAL, + (errcode(ERRCODE_TOO_MANY_CONNECTIONS), + errmsg("too many connections for database \"%s\"", + name))); + } + + /* + * OK, we're golden. Next to-do item is to save the encoding info out of + * the pg_database tuple. + */ + SetDatabaseEncoding(dbform->encoding); + /* Record it as a GUC internal option, too */ + SetConfigOption("server_encoding", GetDatabaseEncodingName(), + PGC_INTERNAL, PGC_S_OVERRIDE); + /* If we have no other source of client_encoding, use server encoding */ + SetConfigOption("client_encoding", GetDatabaseEncodingName(), + PGC_BACKEND, PGC_S_DYNAMIC_DEFAULT); + + /* assign locale variables */ + collate = NameStr(dbform->datcollate); + ctype = NameStr(dbform->datctype); + + if (pg_perm_setlocale(LC_COLLATE, collate) == NULL) + ereport(FATAL, + (errmsg("database locale is incompatible with operating system"), + errdetail("The database was initialized with LC_COLLATE \"%s\", " + " which is not recognized by setlocale().", collate), + errhint("Recreate the database with another locale or install the missing locale."))); + + if (pg_perm_setlocale(LC_CTYPE, ctype) == NULL) + ereport(FATAL, + (errmsg("database locale is incompatible with operating system"), + errdetail("The database was initialized with LC_CTYPE \"%s\", " + " which is not recognized by setlocale().", ctype), + errhint("Recreate the database with another locale or install the missing locale."))); + + /* Make the locale settings visible as GUC variables, too */ + SetConfigOption("lc_collate", collate, PGC_INTERNAL, PGC_S_OVERRIDE); + SetConfigOption("lc_ctype", ctype, PGC_INTERNAL, PGC_S_OVERRIDE); + + check_strxfrm_bug(); + + ReleaseSysCache(tup); +} + + + +/* -------------------------------- + * InitCommunication + * + * This routine initializes stuff needed for ipc, locking, etc. + * it should be called something more informative. + * -------------------------------- + */ +static void +InitCommunication(void) +{ + /* + * initialize shared memory and semaphores appropriately. + */ + if (!IsUnderPostmaster) /* postmaster already did this */ + { + /* + * We're running a postgres bootstrap process or a standalone backend, + * so we need to set up shmem. + */ + CreateSharedMemoryAndSemaphores(); + } +} + + +/* + * pg_split_opts -- split a string of options and append it to an argv array + * + * The caller is responsible for ensuring the argv array is large enough. The + * maximum possible number of arguments added by this routine is + * (strlen(optstr) + 1) / 2. + * + * Because some option values can contain spaces we allow escaping using + * backslashes, with \\ representing a literal backslash. + */ +void +pg_split_opts(char **argv, int *argcp, const char *optstr) +{ + StringInfoData s; + + initStringInfo(&s); + + while (*optstr) + { + bool last_was_escape = false; + + resetStringInfo(&s); + + /* skip over leading space */ + while (isspace((unsigned char) *optstr)) + optstr++; + + if (*optstr == '\0') + break; + + /* + * Parse a single option, stopping at the first space, unless it's + * escaped. + */ + while (*optstr) + { + if (isspace((unsigned char) *optstr) && !last_was_escape) + break; + + if (!last_was_escape && *optstr == '\\') + last_was_escape = true; + else + { + last_was_escape = false; + appendStringInfoChar(&s, *optstr); + } + + optstr++; + } + + /* now store the option in the next argv[] position */ + argv[(*argcp)++] = pstrdup(s.data); + } + + pfree(s.data); +} + +/* + * Initialize MaxBackends value from config options. + * + * This must be called after modules have had the chance to register background + * workers in shared_preload_libraries, and before shared memory size is + * determined. + * + * Note that in EXEC_BACKEND environment, the value is passed down from + * postmaster to subprocesses via BackendParameters in SubPostmasterMain; only + * postmaster itself and processes not under postmaster control should call + * this. + */ +void +InitializeMaxBackends(void) +{ + Assert(MaxBackends == 0); + + /* the extra unit accounts for the autovacuum launcher */ + MaxBackends = MaxConnections + autovacuum_max_workers + 1 + + max_worker_processes + max_wal_senders; + + /* internal error because the values were all checked previously */ + if (MaxBackends > MAX_BACKENDS) + elog(ERROR, "too many backends configured"); +} + +/* + * Early initialization of a backend (either standalone or under postmaster). + * This happens even before InitPostgres. + * + * This is separate from InitPostgres because it is also called by auxiliary + * processes, such as the background writer process, which may not call + * InitPostgres at all. + */ +void +BaseInit(void) +{ + /* + * Attach to shared memory and semaphores, and initialize our + * input/output/debugging file descriptors. + */ + InitCommunication(); + DebugFileOpen(); + + /* Do local initialization of file, storage and buffer managers */ + InitFileAccess(); + InitSync(); + smgrinit(); + InitBufferPoolAccess(); +} + + +/* -------------------------------- + * InitPostgres + * Initialize POSTGRES. + * + * The database can be specified by name, using the in_dbname parameter, or by + * OID, using the dboid parameter. In the latter case, the actual database + * name can be returned to the caller in out_dbname. If out_dbname isn't + * NULL, it must point to a buffer of size NAMEDATALEN. + * + * Similarly, the username can be passed by name, using the username parameter, + * or by OID using the useroid parameter. + * + * In bootstrap mode no parameters are used. The autovacuum launcher process + * doesn't use any parameters either, because it only goes far enough to be + * able to read pg_database; it doesn't connect to any particular database. + * In walsender mode only username is used. + * + * As of PostgreSQL 8.2, we expect InitProcess() was already called, so we + * already have a PGPROC struct ... but it's not completely filled in yet. + * + * Note: + * Be very careful with the order of calls in the InitPostgres function. + * -------------------------------- + */ +void +InitPostgres(const char *in_dbname, Oid dboid, const char *username, + Oid useroid, char *out_dbname, bool override_allow_connections) +{ + bool bootstrap = IsBootstrapProcessingMode(); + bool am_superuser; + char *fullpath; + char dbname[NAMEDATALEN]; + + elog(DEBUG3, "InitPostgres"); + + /* + * Add my PGPROC struct to the ProcArray. + * + * Once I have done this, I am visible to other backends! + */ + InitProcessPhase2(); + + /* + * Initialize my entry in the shared-invalidation manager's array of + * per-backend data. + * + * Sets up MyBackendId, a unique backend identifier. + */ + MyBackendId = InvalidBackendId; + + SharedInvalBackendInit(false); + + if (MyBackendId > MaxBackends || MyBackendId <= 0) + elog(FATAL, "bad backend ID: %d", MyBackendId); + + /* Now that we have a BackendId, we can participate in ProcSignal */ + ProcSignalInit(MyBackendId); + + /* + * Also set up timeout handlers needed for backend operation. We need + * these in every case except bootstrap. + */ + if (!bootstrap) + { + RegisterTimeout(DEADLOCK_TIMEOUT, CheckDeadLockAlert); + RegisterTimeout(STATEMENT_TIMEOUT, StatementTimeoutHandler); + RegisterTimeout(LOCK_TIMEOUT, LockTimeoutHandler); + RegisterTimeout(IDLE_IN_TRANSACTION_SESSION_TIMEOUT, + IdleInTransactionSessionTimeoutHandler); + RegisterTimeout(IDLE_SESSION_TIMEOUT, IdleSessionTimeoutHandler); + RegisterTimeout(CLIENT_CONNECTION_CHECK_TIMEOUT, ClientCheckTimeoutHandler); + } + + /* + * bufmgr needs another initialization call too + */ + InitBufferPoolBackend(); + + /* + * Initialize local process's access to XLOG. + */ + if (IsUnderPostmaster) + { + /* + * The postmaster already started the XLOG machinery, but we need to + * call InitXLOGAccess(), if the system isn't in hot-standby mode. + * This is handled by calling RecoveryInProgress and ignoring the + * result. + */ + (void) RecoveryInProgress(); + } + else + { + /* + * We are either a bootstrap process or a standalone backend. Either + * way, start up the XLOG machinery, and register to have it closed + * down at exit. + * + * We don't yet have an aux-process resource owner, but StartupXLOG + * and ShutdownXLOG will need one. Hence, create said resource owner + * (and register a callback to clean it up after ShutdownXLOG runs). + */ + CreateAuxProcessResourceOwner(); + + StartupXLOG(); + /* Release (and warn about) any buffer pins leaked in StartupXLOG */ + ReleaseAuxProcessResources(true); + /* Reset CurrentResourceOwner to nothing for the moment */ + CurrentResourceOwner = NULL; + + on_shmem_exit(ShutdownXLOG, 0); + } + + /* + * Initialize the relation cache and the system catalog caches. Note that + * no catalog access happens here; we only set up the hashtable structure. + * We must do this before starting a transaction because transaction abort + * would try to touch these hashtables. + */ + RelationCacheInitialize(); + InitCatalogCache(); + InitPlanCache(); + + /* Initialize portal manager */ + EnablePortalManager(); + + /* Initialize stats collection --- must happen before first xact */ + if (!bootstrap) + pgstat_initialize(); + + /* Initialize status reporting */ + if (!bootstrap) + pgstat_beinit(); + + /* + * Load relcache entries for the shared system catalogs. This must create + * at least entries for pg_database and catalogs used for authentication. + */ + RelationCacheInitializePhase2(); + + /* + * Set up process-exit callback to do pre-shutdown cleanup. This is the + * first before_shmem_exit callback we register; thus, this will be the + * last thing we do before low-level modules like the buffer manager begin + * to close down. We need to have this in place before we begin our first + * transaction --- if we fail during the initialization transaction, as is + * entirely possible, we need the AbortTransaction call to clean up. + */ + before_shmem_exit(ShutdownPostgres, 0); + + /* The autovacuum launcher is done here */ + if (IsAutoVacuumLauncherProcess()) + { + /* report this backend in the PgBackendStatus array */ + pgstat_bestart(); + + return; + } + + /* + * Start a new transaction here before first access to db, and get a + * snapshot. We don't have a use for the snapshot itself, but we're + * interested in the secondary effect that it sets RecentGlobalXmin. (This + * is critical for anything that reads heap pages, because HOT may decide + * to prune them even if the process doesn't attempt to modify any + * tuples.) + * + * FIXME: This comment is inaccurate / the code buggy. A snapshot that is + * not pushed/active does not reliably prevent HOT pruning (->xmin could + * e.g. be cleared when cache invalidations are processed). + */ + if (!bootstrap) + { + /* statement_timestamp must be set for timeouts to work correctly */ + SetCurrentStatementStartTimestamp(); + StartTransactionCommand(); + + /* + * transaction_isolation will have been set to the default by the + * above. If the default is "serializable", and we are in hot + * standby, we will fail if we don't change it to something lower. + * Fortunately, "read committed" is plenty good enough. + */ + XactIsoLevel = XACT_READ_COMMITTED; + + (void) GetTransactionSnapshot(); + } + + /* + * Perform client authentication if necessary, then figure out our + * postgres user ID, and see if we are a superuser. + * + * In standalone mode and in autovacuum worker processes, we use a fixed + * ID, otherwise we figure it out from the authenticated user name. + */ + if (bootstrap || IsAutoVacuumWorkerProcess()) + { + InitializeSessionUserIdStandalone(); + am_superuser = true; + } + else if (!IsUnderPostmaster) + { + InitializeSessionUserIdStandalone(); + am_superuser = true; + if (!ThereIsAtLeastOneRole()) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("no roles are defined in this database system"), + errhint("You should immediately run CREATE USER \"%s\" SUPERUSER;.", + username != NULL ? username : "postgres"))); + } + else if (IsBackgroundWorker) + { + if (username == NULL && !OidIsValid(useroid)) + { + InitializeSessionUserIdStandalone(); + am_superuser = true; + } + else + { + InitializeSessionUserId(username, useroid); + am_superuser = superuser(); + } + } + else + { + /* normal multiuser case */ + Assert(MyProcPort != NULL); + PerformAuthentication(MyProcPort); + InitializeSessionUserId(username, useroid); + am_superuser = superuser(); + } + + /* + * If we're trying to shut down, only superusers can connect, and new + * replication connections are not allowed. + */ + if ((!am_superuser || am_walsender) && + MyProcPort != NULL && + MyProcPort->canAcceptConnections == CAC_SUPERUSER) + { + if (am_walsender) + ereport(FATAL, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("new replication connections are not allowed during database shutdown"))); + else + ereport(FATAL, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to connect during database shutdown"))); + } + + /* + * Binary upgrades only allowed super-user connections + */ + if (IsBinaryUpgrade && !am_superuser) + { + ereport(FATAL, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to connect in binary upgrade mode"))); + } + + /* + * The last few connection slots are reserved for superusers. Replication + * connections are drawn from slots reserved with max_wal_senders and not + * limited by max_connections or superuser_reserved_connections. + */ + if (!am_superuser && !am_walsender && + ReservedBackends > 0 && + !HaveNFreeProcs(ReservedBackends)) + ereport(FATAL, + (errcode(ERRCODE_TOO_MANY_CONNECTIONS), + errmsg("remaining connection slots are reserved for non-replication superuser connections"))); + + /* Check replication permissions needed for walsender processes. */ + if (am_walsender) + { + Assert(!bootstrap); + + if (!superuser() && !has_rolreplication(GetUserId())) + ereport(FATAL, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser or replication role to start walsender"))); + } + + /* + * If this is a plain walsender only supporting physical replication, we + * don't want to connect to any particular database. Just finish the + * backend startup by processing any options from the startup packet, and + * we're done. + */ + if (am_walsender && !am_db_walsender) + { + /* process any options passed in the startup packet */ + if (MyProcPort != NULL) + process_startup_options(MyProcPort, am_superuser); + + /* Apply PostAuthDelay as soon as we've read all options */ + if (PostAuthDelay > 0) + pg_usleep(PostAuthDelay * 1000000L); + + /* initialize client encoding */ + InitializeClientEncoding(); + + /* report this backend in the PgBackendStatus array */ + pgstat_bestart(); + + /* close the transaction we started above */ + CommitTransactionCommand(); + + return; + } + + /* + * Set up the global variables holding database id and default tablespace. + * But note we won't actually try to touch the database just yet. + * + * We take a shortcut in the bootstrap case, otherwise we have to look up + * the db's entry in pg_database. + */ + if (bootstrap) + { + MyDatabaseId = TemplateDbOid; + MyDatabaseTableSpace = DEFAULTTABLESPACE_OID; + } + else if (in_dbname != NULL) + { + HeapTuple tuple; + Form_pg_database dbform; + + tuple = GetDatabaseTuple(in_dbname); + if (!HeapTupleIsValid(tuple)) + ereport(FATAL, + (errcode(ERRCODE_UNDEFINED_DATABASE), + errmsg("database \"%s\" does not exist", in_dbname))); + dbform = (Form_pg_database) GETSTRUCT(tuple); + MyDatabaseId = dbform->oid; + MyDatabaseTableSpace = dbform->dattablespace; + /* take database name from the caller, just for paranoia */ + strlcpy(dbname, in_dbname, sizeof(dbname)); + } + else if (OidIsValid(dboid)) + { + /* caller specified database by OID */ + HeapTuple tuple; + Form_pg_database dbform; + + tuple = GetDatabaseTupleByOid(dboid); + if (!HeapTupleIsValid(tuple)) + ereport(FATAL, + (errcode(ERRCODE_UNDEFINED_DATABASE), + errmsg("database %u does not exist", dboid))); + dbform = (Form_pg_database) GETSTRUCT(tuple); + MyDatabaseId = dbform->oid; + MyDatabaseTableSpace = dbform->dattablespace; + Assert(MyDatabaseId == dboid); + strlcpy(dbname, NameStr(dbform->datname), sizeof(dbname)); + /* pass the database name back to the caller */ + if (out_dbname) + strcpy(out_dbname, dbname); + } + else + { + /* + * If this is a background worker not bound to any particular + * database, we're done now. Everything that follows only makes sense + * if we are bound to a specific database. We do need to close the + * transaction we started before returning. + */ + if (!bootstrap) + { + pgstat_bestart(); + CommitTransactionCommand(); + } + return; + } + + /* + * Now, take a writer's lock on the database we are trying to connect to. + * If there is a concurrently running DROP DATABASE on that database, this + * will block us until it finishes (and has committed its update of + * pg_database). + * + * Note that the lock is not held long, only until the end of this startup + * transaction. This is OK since we will advertise our use of the + * database in the ProcArray before dropping the lock (in fact, that's the + * next thing to do). Anyone trying a DROP DATABASE after this point will + * see us in the array once they have the lock. Ordering is important for + * this because we don't want to advertise ourselves as being in this + * database until we have the lock; otherwise we create what amounts to a + * deadlock with CountOtherDBBackends(). + * + * Note: use of RowExclusiveLock here is reasonable because we envision + * our session as being a concurrent writer of the database. If we had a + * way of declaring a session as being guaranteed-read-only, we could use + * AccessShareLock for such sessions and thereby not conflict against + * CREATE DATABASE. + */ + if (!bootstrap) + LockSharedObject(DatabaseRelationId, MyDatabaseId, 0, + RowExclusiveLock); + + /* + * Now we can mark our PGPROC entry with the database ID. + * + * We assume this is an atomic store so no lock is needed; though actually + * things would work fine even if it weren't atomic. Anyone searching the + * ProcArray for this database's ID should hold the database lock, so they + * would not be executing concurrently with this store. A process looking + * for another database's ID could in theory see a chance match if it read + * a partially-updated databaseId value; but as long as all such searches + * wait and retry, as in CountOtherDBBackends(), they will certainly see + * the correct value on their next try. + */ + MyProc->databaseId = MyDatabaseId; + + /* + * We established a catalog snapshot while reading pg_authid and/or + * pg_database; but until we have set up MyDatabaseId, we won't react to + * incoming sinval messages for unshared catalogs, so we won't realize it + * if the snapshot has been invalidated. Assume it's no good anymore. + */ + InvalidateCatalogSnapshot(); + + /* + * Recheck pg_database to make sure the target database hasn't gone away. + * If there was a concurrent DROP DATABASE, this ensures we will die + * cleanly without creating a mess. + */ + if (!bootstrap) + { + HeapTuple tuple; + + tuple = GetDatabaseTuple(dbname); + if (!HeapTupleIsValid(tuple) || + MyDatabaseId != ((Form_pg_database) GETSTRUCT(tuple))->oid || + MyDatabaseTableSpace != ((Form_pg_database) GETSTRUCT(tuple))->dattablespace) + ereport(FATAL, + (errcode(ERRCODE_UNDEFINED_DATABASE), + errmsg("database \"%s\" does not exist", dbname), + errdetail("It seems to have just been dropped or renamed."))); + } + + /* + * Now we should be able to access the database directory safely. Verify + * it's there and looks reasonable. + */ + fullpath = GetDatabasePath(MyDatabaseId, MyDatabaseTableSpace); + + if (!bootstrap) + { + if (access(fullpath, F_OK) == -1) + { + if (errno == ENOENT) + ereport(FATAL, + (errcode(ERRCODE_UNDEFINED_DATABASE), + errmsg("database \"%s\" does not exist", + dbname), + errdetail("The database subdirectory \"%s\" is missing.", + fullpath))); + else + ereport(FATAL, + (errcode_for_file_access(), + errmsg("could not access directory \"%s\": %m", + fullpath))); + } + + ValidatePgVersion(fullpath); + } + + SetDatabasePath(fullpath); + + /* + * It's now possible to do real access to the system catalogs. + * + * Load relcache entries for the system catalogs. This must create at + * least the minimum set of "nailed-in" cache entries. + */ + RelationCacheInitializePhase3(); + + /* set up ACL framework (so CheckMyDatabase can check permissions) */ + initialize_acl(); + + /* + * Re-read the pg_database row for our database, check permissions and set + * up database-specific GUC settings. We can't do this until all the + * database-access infrastructure is up. (Also, it wants to know if the + * user is a superuser, so the above stuff has to happen first.) + */ + if (!bootstrap) + CheckMyDatabase(dbname, am_superuser, override_allow_connections); + + /* + * Now process any command-line switches and any additional GUC variable + * settings passed in the startup packet. We couldn't do this before + * because we didn't know if client is a superuser. + */ + if (MyProcPort != NULL) + process_startup_options(MyProcPort, am_superuser); + + /* Process pg_db_role_setting options */ + process_settings(MyDatabaseId, GetSessionUserId()); + + /* Apply PostAuthDelay as soon as we've read all options */ + if (PostAuthDelay > 0) + pg_usleep(PostAuthDelay * 1000000L); + + /* + * Initialize various default states that can't be set up until we've + * selected the active user and gotten the right GUC settings. + */ + + /* set default namespace search path */ + InitializeSearchPath(); + + /* initialize client encoding */ + InitializeClientEncoding(); + + /* Initialize this backend's session state. */ + InitializeSession(); + + /* report this backend in the PgBackendStatus array */ + if (!bootstrap) + pgstat_bestart(); + + /* close the transaction we started above */ + if (!bootstrap) + CommitTransactionCommand(); +} + +/* + * Process any command-line switches and any additional GUC variable + * settings passed in the startup packet. + */ +static void +process_startup_options(Port *port, bool am_superuser) +{ + GucContext gucctx; + ListCell *gucopts; + + gucctx = am_superuser ? PGC_SU_BACKEND : PGC_BACKEND; + + /* + * First process any command-line switches that were included in the + * startup packet, if we are in a regular backend. + */ + if (port->cmdline_options != NULL) + { + /* + * The maximum possible number of commandline arguments that could + * come from port->cmdline_options is (strlen + 1) / 2; see + * pg_split_opts(). + */ + char **av; + int maxac; + int ac; + + maxac = 2 + (strlen(port->cmdline_options) + 1) / 2; + + av = (char **) palloc(maxac * sizeof(char *)); + ac = 0; + + av[ac++] = "postgres"; + + pg_split_opts(av, &ac, port->cmdline_options); + + av[ac] = NULL; + + Assert(ac < maxac); + + (void) process_postgres_switches(ac, av, gucctx, NULL); + } + + /* + * Process any additional GUC variable settings passed in startup packet. + * These are handled exactly like command-line variables. + */ + gucopts = list_head(port->guc_options); + while (gucopts) + { + char *name; + char *value; + + name = lfirst(gucopts); + gucopts = lnext(port->guc_options, gucopts); + + value = lfirst(gucopts); + gucopts = lnext(port->guc_options, gucopts); + + SetConfigOption(name, value, gucctx, PGC_S_CLIENT); + } +} + +/* + * Load GUC settings from pg_db_role_setting. + * + * We try specific settings for the database/role combination, as well as + * general for this database and for this user. + */ +static void +process_settings(Oid databaseid, Oid roleid) +{ + Relation relsetting; + Snapshot snapshot; + + if (!IsUnderPostmaster) + return; + + relsetting = table_open(DbRoleSettingRelationId, AccessShareLock); + + /* read all the settings under the same snapshot for efficiency */ + snapshot = RegisterSnapshot(GetCatalogSnapshot(DbRoleSettingRelationId)); + + /* Later settings are ignored if set earlier. */ + ApplySetting(snapshot, databaseid, roleid, relsetting, PGC_S_DATABASE_USER); + ApplySetting(snapshot, InvalidOid, roleid, relsetting, PGC_S_USER); + ApplySetting(snapshot, databaseid, InvalidOid, relsetting, PGC_S_DATABASE); + ApplySetting(snapshot, InvalidOid, InvalidOid, relsetting, PGC_S_GLOBAL); + + UnregisterSnapshot(snapshot); + table_close(relsetting, AccessShareLock); +} + +/* + * Backend-shutdown callback. Do cleanup that we want to be sure happens + * before all the supporting modules begin to nail their doors shut via + * their own callbacks. + * + * User-level cleanup, such as temp-relation removal and UNLISTEN, happens + * via separate callbacks that execute before this one. We don't combine the + * callbacks because we still want this one to happen if the user-level + * cleanup fails. + */ +static void +ShutdownPostgres(int code, Datum arg) +{ + /* Make sure we've killed any active transaction */ + AbortOutOfAnyTransaction(); + + /* + * User locks are not released by transaction end, so be sure to release + * them explicitly. + */ + LockReleaseAll(USER_LOCKMETHOD, true); +} + + +/* + * STATEMENT_TIMEOUT handler: trigger a query-cancel interrupt. + */ +static void +StatementTimeoutHandler(void) +{ + int sig = SIGINT; + + /* + * During authentication the timeout is used to deal with + * authentication_timeout - we want to quit in response to such timeouts. + */ + if (ClientAuthInProgress) + sig = SIGTERM; + +#ifdef HAVE_SETSID + /* try to signal whole process group */ + kill(-MyProcPid, sig); +#endif + kill(MyProcPid, sig); +} + +/* + * LOCK_TIMEOUT handler: trigger a query-cancel interrupt. + */ +static void +LockTimeoutHandler(void) +{ +#ifdef HAVE_SETSID + /* try to signal whole process group */ + kill(-MyProcPid, SIGINT); +#endif + kill(MyProcPid, SIGINT); +} + +static void +IdleInTransactionSessionTimeoutHandler(void) +{ + IdleInTransactionSessionTimeoutPending = true; + InterruptPending = true; + SetLatch(MyLatch); +} + +static void +IdleSessionTimeoutHandler(void) +{ + IdleSessionTimeoutPending = true; + InterruptPending = true; + SetLatch(MyLatch); +} + +static void +ClientCheckTimeoutHandler(void) +{ + CheckClientConnectionPending = true; + InterruptPending = true; + SetLatch(MyLatch); +} + +/* + * Returns true if at least one role is defined in this database cluster. + */ +static bool +ThereIsAtLeastOneRole(void) +{ + Relation pg_authid_rel; + TableScanDesc scan; + bool result; + + pg_authid_rel = table_open(AuthIdRelationId, AccessShareLock); + + scan = table_beginscan_catalog(pg_authid_rel, 0, NULL); + result = (heap_getnext(scan, ForwardScanDirection) != NULL); + + table_endscan(scan); + table_close(pg_authid_rel, AccessShareLock); + + return result; +} |