From 46651ce6fe013220ed397add242004d764fc0153 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 4 May 2024 14:15:05 +0200 Subject: Adding upstream version 14.5. Signed-off-by: Daniel Baumann --- src/backend/utils/init/postinit.c | 1278 +++++++++++++++++++++++++++++++++++++ 1 file changed, 1278 insertions(+) create mode 100644 src/backend/utils/init/postinit.c (limited to 'src/backend/utils/init/postinit.c') 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 +#include +#include + +#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; +} -- cgit v1.2.3