mirror of
https://github.com/postgres/postgres.git
synced 2025-05-06 19:59:18 +03:00
Previously tables declared WITH OIDS, including a significant fraction of the catalog tables, stored the oid column not as a normal column, but as part of the tuple header. This special column was not shown by default, which was somewhat odd, as it's often (consider e.g. pg_class.oid) one of the more important parts of a row. Neither pg_dump nor COPY included the contents of the oid column by default. The fact that the oid column was not an ordinary column necessitated a significant amount of special case code to support oid columns. That already was painful for the existing, but upcoming work aiming to make table storage pluggable, would have required expanding and duplicating that "specialness" significantly. WITH OIDS has been deprecated since 2005 (commit ff02d0a05280e0). Remove it. Removing includes: - CREATE TABLE and ALTER TABLE syntax for declaring the table to be WITH OIDS has been removed (WITH (oids[ = true]) will error out) - pg_dump does not support dumping tables declared WITH OIDS and will issue a warning when dumping one (and ignore the oid column). - restoring an pg_dump archive with pg_restore will warn when restoring a table with oid contents (and ignore the oid column) - COPY will refuse to load binary dump that includes oids. - pg_upgrade will error out when encountering tables declared WITH OIDS, they have to be altered to remove the oid column first. - Functionality to access the oid of the last inserted row (like plpgsql's RESULT_OID, spi's SPI_lastoid, ...) has been removed. The syntax for declaring a table WITHOUT OIDS (or WITH (oids = false) for CREATE TABLE) is still supported. While that requires a bit of support code, it seems unnecessary to break applications / dumps that do not use oids, and are explicit about not using them. The biggest user of WITH OID columns was postgres' catalog. This commit changes all 'magic' oid columns to be columns that are normally declared and stored. To reduce unnecessary query breakage all the newly added columns are still named 'oid', even if a table's column naming scheme would indicate 'reloid' or such. This obviously requires adapting a lot code, mostly replacing oid access via HeapTupleGetOid() with access to the underlying Form_pg_*->oid column. The bootstrap process now assigns oids for all oid columns in genbki.pl that do not have an explicit value (starting at the largest oid previously used), only oids assigned later by oids will be above FirstBootstrapObjectId. As the oid column now is a normal column the special bootstrap syntax for oids has been removed. Oids are not automatically assigned during insertion anymore, all backend code explicitly assigns oids with GetNewOidWithIndex(). For the rare case that insertions into the catalog via SQL are called for the new pg_nextoid() function can be used (which only works on catalog tables). The fact that oid columns on system tables are now normal columns means that they will be included in the set of columns expanded by * (i.e. SELECT * FROM pg_class will now include the table's oid, previously it did not). It'd not technically be hard to hide oid column by default, but that'd mean confusing behavior would either have to be carried forward forever, or it'd cause breakage down the line. While it's not unlikely that further adjustments are needed, the scope/invasiveness of the patch makes it worthwhile to get merge this now. It's painful to maintain externally, too complicated to commit after the code code freeze, and a dependency of a number of other patches. Catversion bump, for obvious reasons. Author: Andres Freund, with contributions by John Naylor Discussion: https://postgr.es/m/20180930034810.ywp2c7awz7opzcfr@alap3.anarazel.de
1624 lines
46 KiB
C
1624 lines
46 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* miscinit.c
|
|
* miscellaneous initialization support stuff
|
|
*
|
|
* Portions Copyright (c) 1996-2018, 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>
|
|
#ifdef HAVE_UTIME_H
|
|
#include <utime.h>
|
|
#endif
|
|
|
|
#include "access/htup_details.h"
|
|
#include "catalog/pg_authid.h"
|
|
#include "common/file_perm.h"
|
|
#include "libpq/libpq.h"
|
|
#include "mb/pg_wchar.h"
|
|
#include "miscadmin.h"
|
|
#include "pgstat.h"
|
|
#include "postmaster/autovacuum.h"
|
|
#include "postmaster/postmaster.h"
|
|
#include "storage/fd.h"
|
|
#include "storage/ipc.h"
|
|
#include "storage/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;
|
|
|
|
/* 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;
|
|
|
|
|
|
/* ----------------------------------------------------------------
|
|
* 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;
|
|
|
|
/*
|
|
* 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 */
|
|
|
|
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();
|
|
|
|
/* Initialize process-local latch support */
|
|
InitializeLatchSupport();
|
|
MyLatch = &LocalLatchData;
|
|
InitLatch(MyLatch);
|
|
|
|
/*
|
|
* 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
|
|
|
|
/* 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);
|
|
|
|
/* 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, 1, 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, 1, WL_LATCH_SET, MyLatch);
|
|
|
|
SetLatch(MyLatch);
|
|
}
|
|
|
|
/*
|
|
* 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. This
|
|
* bit prevents not only SET ROLE, but various other changes of session state
|
|
* that normally is unprotected but might possibly be used to subvert the
|
|
* calling session later. An example is replacing an existing prepared
|
|
* statement with new code, which will then be executed with the outer
|
|
* session's permissions when the prepared statement is next used. Since
|
|
* these restrictions are fairly draconian, we apply them only in contexts
|
|
* where the called functions are really supposed to be side-effect-free
|
|
* anyway, such as VACUUM/ANALYZE/REINDEX.
|
|
*
|
|
* 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 miscadmin.h for documentation of the contents
|
|
* of these lockfiles.
|
|
*
|
|
* On successful lockfile creation, a proc_exit callback to remove the
|
|
* lockfile is automatically created.
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
/*
|
|
* proc_exit callback to remove 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("If you're sure there are no old "
|
|
"server processes still running, remove "
|
|
"the shared memory block "
|
|
"or just delete the file \"%s\".",
|
|
filename)));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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 miscadmin.h
|
|
* about the contents. Note that we write the same first five lines into
|
|
* both datadir and socket lockfiles; although more stuff may get added to
|
|
* the datadir lockfile later.
|
|
*/
|
|
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;
|
|
|
|
/*
|
|
* utime() is POSIX standard, utimes() is a common alternative; if we
|
|
* have neither, fall back to actually reading the file (which only
|
|
* sets the access time not mod time, but that should be enough in
|
|
* most cases). In all paths, we ignore errors.
|
|
*/
|
|
#ifdef HAVE_UTIME
|
|
utime(socketLockFile, NULL);
|
|
#else /* !HAVE_UTIME */
|
|
#ifdef HAVE_UTIMES
|
|
utimes(socketLockFile, NULL);
|
|
#else /* !HAVE_UTIMES */
|
|
int fd;
|
|
char buffer[1];
|
|
|
|
fd = open(socketLockFile, O_RDONLY | PG_BINARY, 0);
|
|
if (fd >= 0)
|
|
{
|
|
read(fd, buffer, sizeof(buffer));
|
|
close(fd);
|
|
}
|
|
#endif /* HAVE_UTIMES */
|
|
#endif /* HAVE_UTIME */
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* 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 (lseek(fd, (off_t) 0, SEEK_SET) != 0 ||
|
|
(int) write(fd, destbuffer, len) != 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("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
|
|
}
|