diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/backend/access/transam/varsup.c | 113 | ||||
-rw-r--r-- | src/backend/access/transam/xact.c | 48 | ||||
-rw-r--r-- | src/backend/bootstrap/bootstrap.c | 4 | ||||
-rw-r--r-- | src/backend/commands/dbcommands.c | 28 | ||||
-rw-r--r-- | src/backend/commands/user.c | 492 | ||||
-rw-r--r-- | src/backend/commands/vacuum.c | 52 | ||||
-rw-r--r-- | src/backend/libpq/hba.c | 9 | ||||
-rw-r--r-- | src/backend/postmaster/postmaster.c | 18 | ||||
-rw-r--r-- | src/backend/tcop/postgres.c | 9 | ||||
-rw-r--r-- | src/backend/utils/init/Makefile | 4 | ||||
-rw-r--r-- | src/backend/utils/init/flatfiles.c | 857 | ||||
-rw-r--r-- | src/include/access/transam.h | 16 | ||||
-rw-r--r-- | src/include/commands/user.h | 13 | ||||
-rw-r--r-- | src/include/utils/flatfiles.h | 33 |
14 files changed, 1146 insertions, 550 deletions
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c index bc49bf0ba7f..eb7aeba3818 100644 --- a/src/backend/access/transam/varsup.c +++ b/src/backend/access/transam/varsup.c @@ -6,7 +6,7 @@ * Copyright (c) 2000-2005, PostgreSQL Global Development Group * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/access/transam/varsup.c,v 1.60 2005/01/01 05:43:06 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/access/transam/varsup.c,v 1.61 2005/02/20 02:21:28 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -16,8 +16,10 @@ #include "access/clog.h" #include "access/subtrans.h" #include "access/transam.h" +#include "miscadmin.h" #include "storage/ipc.h" #include "storage/proc.h" +#include "utils/builtins.h" /* Number of OIDs to prefetch (preallocate) per XLOG write */ @@ -47,6 +49,37 @@ GetNewTransactionId(bool isSubXact) xid = ShmemVariableCache->nextXid; /* + * Check to see if it's safe to assign another XID. This protects + * against catastrophic data loss due to XID wraparound. The basic + * rules are: warn if we're past xidWarnLimit, and refuse to execute + * transactions if we're past xidStopLimit, unless we are running in + * a standalone backend (which gives an escape hatch to the DBA who + * ignored all those warnings). + * + * Test is coded to fall out as fast as possible during normal operation, + * ie, when the warn limit is set and we haven't violated it. + */ + if (TransactionIdFollowsOrEquals(xid, ShmemVariableCache->xidWarnLimit) && + TransactionIdIsValid(ShmemVariableCache->xidWarnLimit)) + { + if (IsUnderPostmaster && + TransactionIdFollowsOrEquals(xid, ShmemVariableCache->xidStopLimit)) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("database is shut down to avoid wraparound data loss in database \"%s\"", + NameStr(ShmemVariableCache->limit_datname)), + errhint("Stop the postmaster and use a standalone backend to VACUUM in \"%s\".", + NameStr(ShmemVariableCache->limit_datname)))); + else + ereport(WARNING, + (errmsg("database \"%s\" must be vacuumed within %u transactions", + NameStr(ShmemVariableCache->limit_datname), + ShmemVariableCache->xidWrapLimit - xid), + errhint("To avoid a database shutdown, execute a full-database VACUUM in \"%s\".", + NameStr(ShmemVariableCache->limit_datname)))); + } + + /* * If we are allocating the first XID of a new page of the commit log, * zero out that commit-log page before returning. We must do this * while holding XidGenLock, else another xact could acquire and @@ -137,6 +170,84 @@ ReadNewTransactionId(void) return xid; } +/* + * Determine the last safe XID to allocate given the currently oldest + * datfrozenxid (ie, the oldest XID that might exist in any database + * of our cluster). + */ +void +SetTransactionIdLimit(TransactionId oldest_datfrozenxid, + Name oldest_datname) +{ + TransactionId xidWarnLimit; + TransactionId xidStopLimit; + TransactionId xidWrapLimit; + TransactionId curXid; + + Assert(TransactionIdIsValid(oldest_datfrozenxid)); + + /* + * The place where we actually get into deep trouble is halfway around + * from the oldest potentially-existing XID. (This calculation is + * probably off by one or two counts, because the special XIDs reduce the + * size of the loop a little bit. But we throw in plenty of slop below, + * so it doesn't matter.) + */ + xidWrapLimit = oldest_datfrozenxid + (MaxTransactionId >> 1); + if (xidWrapLimit < FirstNormalTransactionId) + xidWrapLimit += FirstNormalTransactionId; + + /* + * We'll refuse to continue assigning XIDs in interactive mode once + * we get within 1M transactions of data loss. This leaves lots + * of room for the DBA to fool around fixing things in a standalone + * backend, while not being significant compared to total XID space. + * (Note that since vacuuming requires one transaction per table + * cleaned, we had better be sure there's lots of XIDs left...) + */ + xidStopLimit = xidWrapLimit - 1000000; + if (xidStopLimit < FirstNormalTransactionId) + xidStopLimit -= FirstNormalTransactionId; + + /* + * We'll start complaining loudly when we get within 10M transactions + * of the stop point. This is kind of arbitrary, but if you let your + * gas gauge get down to 1% of full, would you be looking for the + * next gas station? We need to be fairly liberal about this number + * because there are lots of scenarios where most transactions are + * done by automatic clients that won't pay attention to warnings. + * (No, we're not gonna make this configurable. If you know enough to + * configure it, you know enough to not get in this kind of trouble in + * the first place.) + */ + xidWarnLimit = xidStopLimit - 10000000; + if (xidWarnLimit < FirstNormalTransactionId) + xidWarnLimit -= FirstNormalTransactionId; + + /* Grab lock for just long enough to set the new limit values */ + LWLockAcquire(XidGenLock, LW_EXCLUSIVE); + ShmemVariableCache->xidWarnLimit = xidWarnLimit; + ShmemVariableCache->xidStopLimit = xidStopLimit; + ShmemVariableCache->xidWrapLimit = xidWrapLimit; + namecpy(&ShmemVariableCache->limit_datname, oldest_datname); + curXid = ShmemVariableCache->nextXid; + LWLockRelease(XidGenLock); + + /* Log the info */ + ereport(LOG, + (errmsg("transaction ID wrap limit is %u, limited by database \"%s\"", + xidWrapLimit, NameStr(*oldest_datname)))); + /* Give an immediate warning if past the wrap warn point */ + if (TransactionIdFollowsOrEquals(curXid, xidWarnLimit)) + ereport(WARNING, + (errmsg("database \"%s\" must be vacuumed within %u transactions", + NameStr(*oldest_datname), + xidWrapLimit - curXid), + errhint("To avoid a database shutdown, execute a full-database VACUUM in \"%s\".", + NameStr(*oldest_datname)))); +} + + /* ---------------------------------------------------------------- * object id generation support * ---------------------------------------------------------------- diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index ec040d8a96f..5813faeea75 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.195 2004/12/31 21:59:29 pgsql Exp $ + * $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.196 2005/02/20 02:21:28 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -28,7 +28,6 @@ #include "commands/async.h" #include "commands/tablecmds.h" #include "commands/trigger.h" -#include "commands/user.h" #include "executor/spi.h" #include "libpq/be-fsstubs.h" #include "miscadmin.h" @@ -36,6 +35,7 @@ #include "storage/proc.h" #include "storage/sinval.h" #include "storage/smgr.h" +#include "utils/flatfiles.h" #include "utils/guc.h" #include "utils/inval.h" #include "utils/memutils.h" @@ -1354,6 +1354,7 @@ StartTransaction(void) * start processing */ s->state = TRANS_START; + s->transactionId = InvalidTransactionId; /* until assigned */ /* * Make sure we've freed any old snapshot, and reset xact state @@ -1464,9 +1465,9 @@ CommitTransaction(void) /* NOTIFY commit must come before lower-level cleanup */ AtCommit_Notify(); - /* Update the flat password file if we changed pg_shadow or pg_group */ + /* Update flat files if we changed pg_database, pg_shadow or pg_group */ /* This should be the last step before commit */ - AtEOXact_UpdatePasswordFile(true); + AtEOXact_UpdateFlatFiles(true); /* Prevent cancel/die interrupt while cleaning up */ HOLD_INTERRUPTS(); @@ -1654,10 +1655,14 @@ AbortTransaction(void) AtAbort_Portals(); AtEOXact_LargeObject(false); /* 'false' means it's abort */ AtAbort_Notify(); - AtEOXact_UpdatePasswordFile(false); + AtEOXact_UpdateFlatFiles(false); - /* Advertise the fact that we aborted in pg_clog. */ - RecordTransactionAbort(); + /* + * Advertise the fact that we aborted in pg_clog (assuming that we + * got as far as assigning an XID to advertise). + */ + if (TransactionIdIsValid(s->transactionId)) + RecordTransactionAbort(); /* * Let others know about no transaction in progress by me. Note that @@ -2034,10 +2039,25 @@ AbortCurrentTransaction(void) switch (s->blockState) { - /* - * we aren't in a transaction, so we do nothing. - */ case TBLOCK_DEFAULT: + if (s->state == TRANS_DEFAULT) + { + /* we are idle, so nothing to do */ + } + else + { + /* + * We can get here after an error during transaction start + * (state will be TRANS_START). Need to clean up the + * incompletely started transaction. First, adjust the + * low-level state to suppress warning message from + * AbortTransaction. + */ + if (s->state == TRANS_START) + s->state = TRANS_INPROGRESS; + AbortTransaction(); + CleanupTransaction(); + } break; /* @@ -3277,8 +3297,8 @@ CommitSubTransaction(void) AtEOSubXact_LargeObject(true, s->subTransactionId, s->parent->subTransactionId); AtSubCommit_Notify(); - AtEOSubXact_UpdatePasswordFile(true, s->subTransactionId, - s->parent->subTransactionId); + AtEOSubXact_UpdateFlatFiles(true, s->subTransactionId, + s->parent->subTransactionId); CallSubXactCallbacks(SUBXACT_EVENT_COMMIT_SUB, s->subTransactionId, s->parent->subTransactionId); @@ -3387,8 +3407,8 @@ AbortSubTransaction(void) AtEOSubXact_LargeObject(false, s->subTransactionId, s->parent->subTransactionId); AtSubAbort_Notify(); - AtEOSubXact_UpdatePasswordFile(false, s->subTransactionId, - s->parent->subTransactionId); + AtEOSubXact_UpdateFlatFiles(false, s->subTransactionId, + s->parent->subTransactionId); /* Advertise the fact that we aborted in pg_clog. */ if (TransactionIdIsValid(s->transactionId)) diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c index 47d68518ec8..279ceba48eb 100644 --- a/src/backend/bootstrap/bootstrap.c +++ b/src/backend/bootstrap/bootstrap.c @@ -8,7 +8,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/bootstrap/bootstrap.c,v 1.198 2005/01/14 21:08:44 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/bootstrap/bootstrap.c,v 1.199 2005/02/20 02:21:31 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -39,6 +39,7 @@ #include "storage/proc.h" #include "tcop/tcopprot.h" #include "utils/builtins.h" +#include "utils/flatfiles.h" #include "utils/fmgroids.h" #include "utils/guc.h" #include "utils/lsyscache.h" @@ -407,6 +408,7 @@ BootstrapMain(int argc, char *argv[]) bootstrap_signals(); StartupXLOG(); LoadFreeSpaceMap(); + BuildFlatFiles(false); proc_exit(0); /* startup done */ case BS_XLOG_BGWRITER: diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c index e5a2a0bcd31..0962e32c5e1 100644 --- a/src/backend/commands/dbcommands.c +++ b/src/backend/commands/dbcommands.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/dbcommands.c,v 1.149 2005/01/27 23:23:55 neilc Exp $ + * $PostgreSQL: pgsql/src/backend/commands/dbcommands.c,v 1.150 2005/02/20 02:21:34 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -39,6 +39,7 @@ #include "utils/acl.h" #include "utils/array.h" #include "utils/builtins.h" +#include "utils/flatfiles.h" #include "utils/fmgroids.h" #include "utils/guc.h" #include "utils/lsyscache.h" @@ -506,6 +507,11 @@ createdb(const CreatedbStmt *stmt) /* Close pg_database, but keep exclusive lock till commit */ heap_close(pg_database_rel, NoLock); + + /* + * Set flag to update flat database file at commit. + */ + database_file_update_needed(); } @@ -642,6 +648,11 @@ dropdb(const char *dbname) /* Close pg_database, but keep exclusive lock till commit */ heap_close(pgdbrel, NoLock); + + /* + * Set flag to update flat database file at commit. + */ + database_file_update_needed(); } @@ -741,6 +752,11 @@ RenameDatabase(const char *oldname, const char *newname) /* Close pg_database, but keep exclusive lock till commit */ heap_close(rel, NoLock); + + /* + * Set flag to update flat database file at commit. + */ + database_file_update_needed(); } @@ -834,6 +850,11 @@ AlterDatabaseSet(AlterDatabaseSetStmt *stmt) /* Close pg_database, but keep exclusive lock till commit */ heap_close(rel, NoLock); + + /* + * We don't bother updating the flat file since ALTER DATABASE SET + * doesn't affect it. + */ } @@ -933,6 +954,11 @@ AlterDatabaseOwner(const char *dbname, AclId newOwnerSysId) /* Close pg_database, but keep exclusive lock till commit */ heap_close(rel, NoLock); + + /* + * We don't bother updating the flat file since ALTER DATABASE OWNER + * doesn't affect it. + */ } diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c index db4f903d144..6136e99519e 100644 --- a/src/backend/commands/user.c +++ b/src/backend/commands/user.c @@ -6,17 +6,12 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/backend/commands/user.c,v 1.148 2005/01/27 23:23:56 neilc Exp $ + * $PostgreSQL: pgsql/src/backend/commands/user.c,v 1.149 2005/02/20 02:21:34 tgl Exp $ * *------------------------------------------------------------------------- */ #include "postgres.h" -#include <sys/stat.h> -#include <fcntl.h> -#include <errno.h> -#include <unistd.h> - #include "access/heapam.h" #include "catalog/catname.h" #include "catalog/indexing.h" @@ -27,50 +22,17 @@ #include "commands/user.h" #include "libpq/crypt.h" #include "miscadmin.h" -#include "storage/fd.h" -#include "storage/pmsignal.h" #include "utils/acl.h" -#include "utils/array.h" #include "utils/builtins.h" +#include "utils/flatfiles.h" #include "utils/fmgroids.h" #include "utils/guc.h" #include "utils/lsyscache.h" #include "utils/syscache.h" -#define PWD_FILE "pg_pwd" -#define USER_GROUP_FILE "pg_group" - - extern bool Password_encryption; -/* - * The need-to-update-files flags are a pair of SubTransactionIds that show - * what level of the subtransaction tree requested the update. To register - * an update, the subtransaction saves its own SubTransactionId in the flag, - * unless the value was already set to a valid SubTransactionId (which implies - * that it or a parent level has already requested the same). If it aborts - * and the value is its SubTransactionId, it resets the flag to - * InvalidSubTransactionId. If it commits, it changes the value to its - * parent's SubTransactionId. This way the value is propagated up to the - * top-level transaction, which will update the files if a valid - * SubTransactionId is detected. - */ -static SubTransactionId user_file_update_subid = InvalidSubTransactionId; -static SubTransactionId group_file_update_subid = InvalidSubTransactionId; - -#define user_file_update_needed() \ - do { \ - if (user_file_update_subid == InvalidSubTransactionId) \ - user_file_update_subid = GetCurrentSubTransactionId(); \ - } while (0) - -#define group_file_update_needed() \ - do { \ - if (group_file_update_subid == InvalidSubTransactionId) \ - group_file_update_subid = GetCurrentSubTransactionId(); \ - } while (0) - static void CheckPgUserAclNotNull(void); static void UpdateGroupMembership(Relation group_rel, HeapTuple group_tuple, @@ -80,454 +42,6 @@ static List *IdArrayToList(IdList *oldarray); /* - * fputs_quote - * - * Outputs string in quotes, with double-quotes duplicated. - * We could use quote_ident(), but that expects a TEXT argument. - */ -static void -fputs_quote(char *str, FILE *fp) -{ - fputc('"', fp); - while (*str) - { - fputc(*str, fp); - if (*str == '"') - fputc('"', fp); - str++; - } - fputc('"', fp); -} - - -/* - * group_getfilename --- get full pathname of group file - * - * Note that result string is palloc'd, and should be freed by the caller. - */ -char * -group_getfilename(void) -{ - int bufsize; - char *pfnam; - - bufsize = strlen(DataDir) + strlen("/global/") + - strlen(USER_GROUP_FILE) + 1; - pfnam = (char *) palloc(bufsize); - snprintf(pfnam, bufsize, "%s/global/%s", DataDir, USER_GROUP_FILE); - - return pfnam; -} - - -/* - * Get full pathname of password file. - * - * Note that result string is palloc'd, and should be freed by the caller. - */ -char * -user_getfilename(void) -{ - int bufsize; - char *pfnam; - - bufsize = strlen(DataDir) + strlen("/global/") + - strlen(PWD_FILE) + 1; - pfnam = (char *) palloc(bufsize); - snprintf(pfnam, bufsize, "%s/global/%s", DataDir, PWD_FILE); - - return pfnam; -} - - -/* - * write_group_file: update the flat group file - */ -static void -write_group_file(Relation grel) -{ - char *filename, - *tempname; - int bufsize; - FILE *fp; - mode_t oumask; - HeapScanDesc scan; - HeapTuple tuple; - TupleDesc dsc = RelationGetDescr(grel); - - /* - * Create a temporary filename to be renamed later. This prevents the - * backend from clobbering the pg_group file while the postmaster - * might be reading from it. - */ - filename = group_getfilename(); - bufsize = strlen(filename) + 12; - tempname = (char *) palloc(bufsize); - snprintf(tempname, bufsize, "%s.%d", filename, MyProcPid); - - oumask = umask((mode_t) 077); - fp = AllocateFile(tempname, "w"); - umask(oumask); - if (fp == NULL) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not write to temporary file \"%s\": %m", tempname))); - - /* - * Read pg_group and write the file. Note we use SnapshotSelf to - * ensure we see all effects of current transaction. (Perhaps could - * do a CommandCounterIncrement beforehand, instead?) - */ - scan = heap_beginscan(grel, SnapshotSelf, 0, NULL); - while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) - { - Datum datum, - grolist_datum; - bool isnull; - char *groname; - IdList *grolist_p; - AclId *aidp; - int i, - j, - num; - char *usename; - bool first_user = true; - - datum = heap_getattr(tuple, Anum_pg_group_groname, dsc, &isnull); - /* ignore NULL groupnames --- shouldn't happen */ - if (isnull) - continue; - groname = NameStr(*DatumGetName(datum)); - - /* - * Check for invalid characters in the group name. - */ - i = strcspn(groname, "\n"); - if (groname[i] != '\0') - { - ereport(LOG, - (errmsg("invalid group name \"%s\"", groname))); - continue; - } - - grolist_datum = heap_getattr(tuple, Anum_pg_group_grolist, dsc, &isnull); - /* Ignore NULL group lists */ - if (isnull) - continue; - - /* be sure the IdList is not toasted */ - grolist_p = DatumGetIdListP(grolist_datum); - - /* scan grolist */ - num = IDLIST_NUM(grolist_p); - aidp = IDLIST_DAT(grolist_p); - for (i = 0; i < num; ++i) - { - tuple = SearchSysCache(SHADOWSYSID, - PointerGetDatum(aidp[i]), - 0, 0, 0); - if (HeapTupleIsValid(tuple)) - { - usename = NameStr(((Form_pg_shadow) GETSTRUCT(tuple))->usename); - - /* - * Check for illegal characters in the user name. - */ - j = strcspn(usename, "\n"); - if (usename[j] != '\0') - { - ereport(LOG, - (errmsg("invalid user name \"%s\"", usename))); - continue; - } - - /* - * File format is: "dbname" "user1" "user2" "user3" - */ - if (first_user) - { - fputs_quote(groname, fp); - fputs("\t", fp); - } - else - fputs(" ", fp); - - first_user = false; - fputs_quote(usename, fp); - - ReleaseSysCache(tuple); - } - } - if (!first_user) - fputs("\n", fp); - /* if IdList was toasted, free detoasted copy */ - if ((Pointer) grolist_p != DatumGetPointer(grolist_datum)) - pfree(grolist_p); - } - heap_endscan(scan); - - if (FreeFile(fp)) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not write to temporary file \"%s\": %m", - tempname))); - - /* - * Rename the temp file to its final name, deleting the old pg_pwd. We - * expect that rename(2) is an atomic action. - */ - if (rename(tempname, filename)) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not rename file \"%s\" to \"%s\": %m", - tempname, filename))); - - pfree(tempname); - pfree(filename); -} - - -/* - * write_user_file: update the flat password file - */ -static void -write_user_file(Relation urel) -{ - char *filename, - *tempname; - int bufsize; - FILE *fp; - mode_t oumask; - HeapScanDesc scan; - HeapTuple tuple; - TupleDesc dsc = RelationGetDescr(urel); - - /* - * Create a temporary filename to be renamed later. This prevents the - * backend from clobbering the pg_pwd file while the postmaster might - * be reading from it. - */ - filename = user_getfilename(); - bufsize = strlen(filename) + 12; - tempname = (char *) palloc(bufsize); - snprintf(tempname, bufsize, "%s.%d", filename, MyProcPid); - - oumask = umask((mode_t) 077); - fp = AllocateFile(tempname, "w"); - umask(oumask); - if (fp == NULL) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not write to temporary file \"%s\": %m", tempname))); - - /* - * Read pg_shadow and write the file. Note we use SnapshotSelf to - * ensure we see all effects of current transaction. (Perhaps could - * do a CommandCounterIncrement beforehand, instead?) - */ - scan = heap_beginscan(urel, SnapshotSelf, 0, NULL); - while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) - { - Datum datum; - bool isnull; - char *usename, - *passwd, - *valuntil; - int i; - - datum = heap_getattr(tuple, Anum_pg_shadow_usename, dsc, &isnull); - /* ignore NULL usernames (shouldn't happen) */ - if (isnull) - continue; - usename = NameStr(*DatumGetName(datum)); - - datum = heap_getattr(tuple, Anum_pg_shadow_passwd, dsc, &isnull); - - /* - * It can be argued that people having a null password shouldn't - * be allowed to connect under password authentication, because - * they need to have a password set up first. If you think - * assuming an empty password in that case is better, change this - * logic to look something like the code for valuntil. - */ - if (isnull) - continue; - - passwd = DatumGetCString(DirectFunctionCall1(textout, datum)); - - datum = heap_getattr(tuple, Anum_pg_shadow_valuntil, dsc, &isnull); - if (isnull) - valuntil = pstrdup(""); - else - valuntil = DatumGetCString(DirectFunctionCall1(abstimeout, datum)); - - /* - * Check for illegal characters in the username and password. - */ - i = strcspn(usename, "\n"); - if (usename[i] != '\0') - { - ereport(LOG, - (errmsg("invalid user name \"%s\"", usename))); - continue; - } - i = strcspn(passwd, "\n"); - if (passwd[i] != '\0') - { - ereport(LOG, - (errmsg("invalid user password \"%s\"", passwd))); - continue; - } - - /* - * The extra columns we emit here are not really necessary. To - * remove them, the parser in backend/libpq/crypt.c would need to - * be adjusted. - */ - fputs_quote(usename, fp); - fputs(" ", fp); - fputs_quote(passwd, fp); - fputs(" ", fp); - fputs_quote(valuntil, fp); - fputs("\n", fp); - - pfree(passwd); - pfree(valuntil); - } - heap_endscan(scan); - - if (FreeFile(fp)) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not write to temporary file \"%s\": %m", - tempname))); - - /* - * Rename the temp file to its final name, deleting the old pg_pwd. We - * expect that rename(2) is an atomic action. - */ - if (rename(tempname, filename)) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not rename file \"%s\" to \"%s\": %m", - tempname, filename))); - - pfree(tempname); - pfree(filename); -} - - -/* - * This trigger is fired whenever someone modifies pg_shadow or pg_group - * via general-purpose INSERT/UPDATE/DELETE commands. - * - * XXX should probably have two separate triggers. - */ -Datum -update_pg_pwd_and_pg_group(PG_FUNCTION_ARGS) -{ - user_file_update_needed(); - group_file_update_needed(); - - return PointerGetDatum(NULL); -} - - -/* - * This routine is called during transaction commit or abort. - * - * On commit, if we've written pg_shadow or pg_group during the current - * transaction, update the flat files and signal the postmaster. - * - * On abort, just reset the static flags so we don't try to do it on the - * next successful commit. - * - * NB: this should be the last step before actual transaction commit. - * If any error aborts the transaction after we run this code, the postmaster - * will still have received and cached the changed data; so minimize the - * window for such problems. - */ -void -AtEOXact_UpdatePasswordFile(bool isCommit) -{ - Relation urel = NULL; - Relation grel = NULL; - - if (user_file_update_subid == InvalidSubTransactionId && - group_file_update_subid == InvalidSubTransactionId) - return; - - if (!isCommit) - { - user_file_update_subid = InvalidSubTransactionId; - group_file_update_subid = InvalidSubTransactionId; - return; - } - - /* - * We use ExclusiveLock to ensure that only one backend writes the - * flat file(s) at a time. That's sufficient because it's okay to - * allow plain reads of the tables in parallel. There is some chance - * of a deadlock here (if we were triggered by a user update of - * pg_shadow or pg_group, which likely won't have gotten a strong - * enough lock), so get the locks we need before writing anything. - */ - if (user_file_update_subid != InvalidSubTransactionId) - urel = heap_openr(ShadowRelationName, ExclusiveLock); - if (group_file_update_subid != InvalidSubTransactionId) - grel = heap_openr(GroupRelationName, ExclusiveLock); - - /* Okay to write the files */ - if (user_file_update_subid != InvalidSubTransactionId) - { - user_file_update_subid = InvalidSubTransactionId; - write_user_file(urel); - heap_close(urel, NoLock); - } - - if (group_file_update_subid != InvalidSubTransactionId) - { - group_file_update_subid = InvalidSubTransactionId; - write_group_file(grel); - heap_close(grel, NoLock); - } - - /* - * Signal the postmaster to reload its password & group-file cache. - */ - SendPostmasterSignal(PMSIGNAL_PASSWORD_CHANGE); -} - -/* - * AtEOSubXact_UpdatePasswordFile - * - * Called at subtransaction end, this routine resets or updates the - * need-to-update-files flags. - */ -void -AtEOSubXact_UpdatePasswordFile(bool isCommit, - SubTransactionId mySubid, - SubTransactionId parentSubid) -{ - if (isCommit) - { - if (user_file_update_subid == mySubid) - user_file_update_subid = parentSubid; - - if (group_file_update_subid == mySubid) - group_file_update_subid = parentSubid; - } - else - { - if (user_file_update_subid == mySubid) - user_file_update_subid = InvalidSubTransactionId; - - if (group_file_update_subid == mySubid) - group_file_update_subid = InvalidSubTransactionId; - } -} - -/* * CREATE USER */ void @@ -1060,7 +574,6 @@ AlterUserSet(AlterUserSetStmt *stmt) } - /* * DROP USER */ @@ -1318,7 +831,6 @@ CheckPgUserAclNotNull(void) } - /* * CREATE GROUP */ diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index d0ccf310cc4..c818d8f9667 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -13,7 +13,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.300 2005/02/15 03:50:07 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.301 2005/02/20 02:21:34 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -32,6 +32,7 @@ #include "catalog/namespace.h" #include "catalog/pg_database.h" #include "catalog/pg_index.h" +#include "commands/dbcommands.h" #include "commands/vacuum.h" #include "executor/executor.h" #include "miscadmin.h" @@ -761,8 +762,13 @@ vac_update_dbstats(Oid dbid, * * Scan pg_database to determine the system-wide oldest datvacuumxid, * and use it to truncate the transaction commit log (pg_clog). - * Also generate a warning if the system-wide oldest datfrozenxid - * seems to be in danger of wrapping around. + * Also update the XID wrap limit point maintained by varsup.c. + * + * We also generate a warning if the system-wide oldest datfrozenxid + * seems to be in danger of wrapping around. This is a long-in-advance + * warning; if we start getting uncomfortably close, GetNewTransactionId + * will generate more-annoying warnings, and ultimately refuse to issue + * any more new XIDs. * * The passed XIDs are simply the ones I just wrote into my pg_database * entry. They're used to initialize the "min" calculations. @@ -778,10 +784,17 @@ vac_truncate_clog(TransactionId vacuumXID, TransactionId frozenXID) HeapScanDesc scan; HeapTuple tuple; int32 age; + NameData oldest_datname; bool vacuumAlreadyWrapped = false; bool frozenAlreadyWrapped = false; + /* init oldest_datname to sync with my frozenXID */ + namestrcpy(&oldest_datname, get_database_name(MyDatabaseId)); + /* + * Note: the "already wrapped" cases should now be impossible due to the + * defenses in GetNewTransactionId, but we keep them anyway. + */ relation = heap_openr(DatabaseRelationName, AccessShareLock); scan = heap_beginscan(relation, SnapshotNow, 0, NULL); @@ -807,7 +820,10 @@ vac_truncate_clog(TransactionId vacuumXID, TransactionId frozenXID) if (TransactionIdPrecedes(myXID, dbform->datfrozenxid)) frozenAlreadyWrapped = true; else if (TransactionIdPrecedes(dbform->datfrozenxid, frozenXID)) + { frozenXID = dbform->datfrozenxid; + namecpy(&oldest_datname, &dbform->datname); + } } } @@ -830,24 +846,30 @@ vac_truncate_clog(TransactionId vacuumXID, TransactionId frozenXID) /* Truncate CLOG to the oldest vacuumxid */ TruncateCLOG(vacuumXID); - /* Give warning about impending wraparound problems */ + /* + * Do not update varsup.c if we seem to have suffered wraparound + * already; the computed XID might be bogus. + */ if (frozenAlreadyWrapped) { ereport(WARNING, (errmsg("some databases have not been vacuumed in over 1 billion transactions"), errhint("Better vacuum them soon, or you may have a wraparound failure."))); + return; } - else - { - age = (int32) (myXID - frozenXID); - if (age > (int32) ((MaxTransactionId >> 3) * 3)) - ereport(WARNING, - (errmsg("some databases have not been vacuumed in %d transactions", - age), - errhint("Better vacuum them within %d transactions, " - "or you may have a wraparound failure.", - (int32) (MaxTransactionId >> 1) - age))); - } + + /* Update the wrap limit for GetNewTransactionId */ + SetTransactionIdLimit(frozenXID, &oldest_datname); + + /* Give warning about impending wraparound problems */ + age = (int32) (myXID - frozenXID); + if (age > (int32) ((MaxTransactionId >> 3) * 3)) + ereport(WARNING, + (errmsg("database \"%s\" must be vacuumed within %u transactions", + NameStr(oldest_datname), + (MaxTransactionId >> 1) - age), + errhint("To avoid a database shutdown, execute a full-database VACUUM in \"%s\".", + NameStr(oldest_datname)))); } diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index 592995025d2..f96eaec0f2d 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -10,13 +10,12 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/libpq/hba.c,v 1.137 2005/02/12 23:53:42 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/libpq/hba.c,v 1.138 2005/02/20 02:21:40 tgl Exp $ * *------------------------------------------------------------------------- */ #include "postgres.h" -#include <errno.h> #include <pwd.h> #include <fcntl.h> #include <sys/param.h> @@ -29,12 +28,12 @@ #include <arpa/inet.h> #include <unistd.h> -#include "commands/user.h" #include "libpq/crypt.h" #include "libpq/libpq.h" #include "miscadmin.h" #include "nodes/pg_list.h" #include "storage/fd.h" +#include "utils/flatfiles.h" #include "utils/guc.h" @@ -936,7 +935,7 @@ load_group(void) group_length = 0; /* Read in the file contents */ - filename = group_getfilename(); + filename = group_getflatfilename(); group_file = AllocateFile(filename, "r"); if (group_file == NULL) @@ -993,7 +992,7 @@ load_user(void) user_length = 0; /* Read in the file contents */ - filename = user_getfilename(); + filename = user_getflatfilename(); user_file = AllocateFile(filename, "r"); if (user_file == NULL) diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index c5f4899c732..7cd9a1b048d 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -37,7 +37,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/postmaster/postmaster.c,v 1.443 2005/01/12 16:38:17 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/postmaster/postmaster.c,v 1.444 2005/02/20 02:21:54 tgl Exp $ * * NOTES * @@ -903,12 +903,10 @@ PostmasterMain(int argc, char *argv[]) pgstat_init(); /* - * Load cached files for client authentication. + * Load configuration files for client authentication. */ load_hba(); load_ident(); - load_user(); - load_group(); /* * We're ready to rock and roll... @@ -1797,6 +1795,8 @@ SIGHUP_handler(SIGNAL_ARGS) if (SysLoggerPID != 0) kill(SysLoggerPID, SIGHUP); /* PgStatPID does not currently need SIGHUP */ + + /* Reload authentication config files too */ load_hba(); load_ident(); @@ -2007,6 +2007,14 @@ reaper(SIGNAL_ARGS) FatalError = false; /* + * Load the flat user/group files into postmaster's caches. + * The startup process has recomputed these from the database + * contents, so we wait till it finishes before loading them. + */ + load_user(); + load_group(); + + /* * Crank up the background writer. It doesn't matter if this * fails, we'll just try again later. */ @@ -2662,7 +2670,7 @@ BackendRun(Port *port) port->remote_port = strdup(remote_port); /* - * In EXEC_BACKEND case, we didn't inherit the contents of pg_hba.c + * In EXEC_BACKEND case, we didn't inherit the contents of pg_hba.conf * etcetera from the postmaster, and have to load them ourselves. * Build the PostmasterContext (which didn't exist before, in this * process) to contain the data. diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index d640c3b602c..58d7bade5c6 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.440 2004/12/31 22:01:16 pgsql Exp $ + * $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.441 2005/02/20 02:21:57 tgl Exp $ * * NOTES * this is the "main" module of the postgres backend and @@ -55,6 +55,7 @@ #include "tcop/pquery.h" #include "tcop/tcopprot.h" #include "tcop/utility.h" +#include "utils/flatfiles.h" #include "utils/guc.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -2706,6 +2707,12 @@ PostgresMain(int argc, char *argv[], const char *username) */ LoadFreeSpaceMap(); on_shmem_exit(DumpFreeSpaceMap, 0); + + /* + * We have to build the flat file for pg_database, but not for + * the user and group tables, since we won't try to do authentication. + */ + BuildFlatFiles(true); } /* diff --git a/src/backend/utils/init/Makefile b/src/backend/utils/init/Makefile index 8415de8b2a4..ffc5ff9e064 100644 --- a/src/backend/utils/init/Makefile +++ b/src/backend/utils/init/Makefile @@ -4,7 +4,7 @@ # Makefile for utils/init # # IDENTIFICATION -# $PostgreSQL: pgsql/src/backend/utils/init/Makefile,v 1.17 2004/05/11 21:57:14 momjian Exp $ +# $PostgreSQL: pgsql/src/backend/utils/init/Makefile,v 1.18 2005/02/20 02:22:00 tgl Exp $ # #------------------------------------------------------------------------- @@ -12,7 +12,7 @@ subdir = src/backend/utils/init top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global -OBJS = globals.o miscinit.o postinit.o +OBJS = flatfiles.o globals.o miscinit.o postinit.o all: SUBSYS.o diff --git a/src/backend/utils/init/flatfiles.c b/src/backend/utils/init/flatfiles.c new file mode 100644 index 00000000000..c54badea8e2 --- /dev/null +++ b/src/backend/utils/init/flatfiles.c @@ -0,0 +1,857 @@ +/*------------------------------------------------------------------------- + * + * flatfiles.c + * Routines for maintaining "flat file" images of the shared catalogs. + * + * We use flat files so that the postmaster and not-yet-fully-started + * backends can look at the contents of pg_database, pg_shadow, and pg_group + * for authentication purposes. This module is responsible for keeping the + * flat-file images as nearly in sync with database reality as possible. + * + * The tricky part of the write_xxx_file() routines in this module is that + * they need to be able to operate in the context of the database startup + * process (which calls BuildFlatFiles()) as well as a normal backend. + * This means for example that we can't assume a fully functional relcache + * and we can't use syscaches at all. The major restriction imposed by + * all that is that there's no way to read an out-of-line-toasted datum, + * because the tuptoaster.c code is not prepared to cope with such an + * environment. Fortunately we can design the shared catalogs in such + * a way that this is OK. + * + * + * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * $PostgreSQL: pgsql/src/backend/utils/init/flatfiles.c,v 1.1 2005/02/20 02:22:00 tgl Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <sys/stat.h> +#include <unistd.h> + +#include "access/heapam.h" +#include "catalog/catname.h" +#include "catalog/pg_database.h" +#include "catalog/pg_group.h" +#include "catalog/pg_namespace.h" +#include "catalog/pg_shadow.h" +#include "catalog/pg_tablespace.h" +#include "commands/trigger.h" +#include "miscadmin.h" +#include "storage/fd.h" +#include "storage/pmsignal.h" +#include "utils/acl.h" +#include "utils/builtins.h" +#include "utils/flatfiles.h" +#include "utils/resowner.h" +#include "utils/syscache.h" + + +#define DATABASE_FLAT_FILE "pg_database" +#define GROUP_FLAT_FILE "pg_group" +#define USER_FLAT_FILE "pg_pwd" + + +/* + * The need-to-update-files flags are SubTransactionIds that show + * what level of the subtransaction tree requested the update. To register + * an update, the subtransaction saves its own SubTransactionId in the flag, + * unless the value was already set to a valid SubTransactionId (which implies + * that it or a parent level has already requested the same). If it aborts + * and the value is its SubTransactionId, it resets the flag to + * InvalidSubTransactionId. If it commits, it changes the value to its + * parent's SubTransactionId. This way the value is propagated up to the + * top-level transaction, which will update the files if a valid + * SubTransactionId is seen at top-level commit. + */ +static SubTransactionId database_file_update_subid = InvalidSubTransactionId; +static SubTransactionId group_file_update_subid = InvalidSubTransactionId; +static SubTransactionId user_file_update_subid = InvalidSubTransactionId; + + +/* + * Mark flat database file as needing an update (because pg_database changed) + */ +void +database_file_update_needed(void) +{ + if (database_file_update_subid == InvalidSubTransactionId) + database_file_update_subid = GetCurrentSubTransactionId(); +} + +/* + * Mark flat group file as needing an update (because pg_group changed) + */ +void +group_file_update_needed(void) +{ + if (group_file_update_subid == InvalidSubTransactionId) + group_file_update_subid = GetCurrentSubTransactionId(); +} + +/* + * Mark flat user file as needing an update (because pg_shadow changed) + */ +void +user_file_update_needed(void) +{ + if (user_file_update_subid == InvalidSubTransactionId) + user_file_update_subid = GetCurrentSubTransactionId(); +} + + +/* + * database_getflatfilename --- get full pathname of database file + * + * Note that result string is palloc'd, and should be freed by the caller. + */ +char * +database_getflatfilename(void) +{ + int bufsize; + char *pfnam; + + bufsize = strlen(DataDir) + strlen("/global/") + + strlen(DATABASE_FLAT_FILE) + 1; + pfnam = (char *) palloc(bufsize); + snprintf(pfnam, bufsize, "%s/global/%s", DataDir, DATABASE_FLAT_FILE); + + return pfnam; +} + +/* + * group_getflatfilename --- get full pathname of group file + * + * Note that result string is palloc'd, and should be freed by the caller. + */ +char * +group_getflatfilename(void) +{ + int bufsize; + char *pfnam; + + bufsize = strlen(DataDir) + strlen("/global/") + + strlen(GROUP_FLAT_FILE) + 1; + pfnam = (char *) palloc(bufsize); + snprintf(pfnam, bufsize, "%s/global/%s", DataDir, GROUP_FLAT_FILE); + + return pfnam; +} + +/* + * Get full pathname of password file. + * + * Note that result string is palloc'd, and should be freed by the caller. + */ +char * +user_getflatfilename(void) +{ + int bufsize; + char *pfnam; + + bufsize = strlen(DataDir) + strlen("/global/") + + strlen(USER_FLAT_FILE) + 1; + pfnam = (char *) palloc(bufsize); + snprintf(pfnam, bufsize, "%s/global/%s", DataDir, USER_FLAT_FILE); + + return pfnam; +} + + +/* + * fputs_quote + * + * Outputs string in quotes, with double-quotes duplicated. + * We could use quote_ident(), but that expects a TEXT argument. + */ +static void +fputs_quote(const char *str, FILE *fp) +{ + fputc('"', fp); + while (*str) + { + fputc(*str, fp); + if (*str == '"') + fputc('"', fp); + str++; + } + fputc('"', fp); +} + +/* + * name_okay + * + * We must disallow newlines in user and group names because + * hba.c's parser won't handle fields split across lines, even if quoted. + */ +static bool +name_okay(const char *str) +{ + int i; + + i = strcspn(str, "\r\n"); + return (str[i] == '\0'); +} + + +/* + * write_database_file: update the flat database file + * + * A side effect is to determine the oldest database's datfrozenxid + * so we can set or update the XID wrap limit. + */ +static void +write_database_file(Relation drel) +{ + char *filename, + *tempname; + int bufsize; + FILE *fp; + mode_t oumask; + HeapScanDesc scan; + HeapTuple tuple; + NameData oldest_datname; + TransactionId oldest_datfrozenxid = InvalidTransactionId; + + /* + * Create a temporary filename to be renamed later. This prevents the + * backend from clobbering the flat file while the postmaster + * might be reading from it. + */ + filename = database_getflatfilename(); + bufsize = strlen(filename) + 12; + tempname = (char *) palloc(bufsize); + snprintf(tempname, bufsize, "%s.%d", filename, MyProcPid); + + oumask = umask((mode_t) 077); + fp = AllocateFile(tempname, "w"); + umask(oumask); + if (fp == NULL) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not write to temporary file \"%s\": %m", + tempname))); + + /* + * Read pg_database and write the file. Note we use SnapshotSelf to + * ensure we see all effects of current transaction. (Perhaps could + * do a CommandCounterIncrement beforehand, instead?) + */ + scan = heap_beginscan(drel, SnapshotSelf, 0, NULL); + while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) + { + Form_pg_database dbform = (Form_pg_database) GETSTRUCT(tuple); + char *datname; + Oid datoid; + TransactionId datfrozenxid; + + datname = NameStr(dbform->datname); + datoid = HeapTupleGetOid(tuple); + datfrozenxid = dbform->datfrozenxid; + + /* + * Identify the oldest datfrozenxid, ignoring databases that are not + * connectable (we assume they are safely frozen). This must match + * the logic in vac_truncate_clog() in vacuum.c. + */ + if (dbform->datallowconn && + TransactionIdIsNormal(datfrozenxid)) + { + if (oldest_datfrozenxid == InvalidTransactionId || + TransactionIdPrecedes(datfrozenxid, oldest_datfrozenxid)) + { + oldest_datfrozenxid = datfrozenxid; + namestrcpy(&oldest_datname, datname); + } + } + + /* + * Check for illegal characters in the database name. + */ + if (!name_okay(datname)) + { + ereport(LOG, + (errmsg("invalid database name \"%s\"", datname))); + continue; + } + + /* + * File format is: "dbname" oid frozenxid + * + * The xid is not needed for backend startup, but may be of use + * for forensic purposes. + */ + fputs_quote(datname, fp); + fprintf(fp, " %u %u\n", datoid, datfrozenxid); + } + heap_endscan(scan); + + if (FreeFile(fp)) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not write to temporary file \"%s\": %m", + tempname))); + + /* + * Rename the temp file to its final name, deleting the old flat file. + * We expect that rename(2) is an atomic action. + */ + if (rename(tempname, filename)) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not rename file \"%s\" to \"%s\": %m", + tempname, filename))); + + pfree(tempname); + pfree(filename); + + /* + * Set the transaction ID wrap limit using the oldest datfrozenxid + */ + if (oldest_datfrozenxid != InvalidTransactionId) + SetTransactionIdLimit(oldest_datfrozenxid, &oldest_datname); +} + + +/* + * write_group_file: update the flat group file + * + * XXX this will never be able to work during system bootstrap: we don't + * have either TOAST support or SysCache support. Need to redefine both + * the catalog and file contents to fix this completely. In the short term + * we can handle everything except an out-of-line-toasted grolist, if we + * change the flat file definition to store numeric sysids instead of + * user names. + */ +static void +write_group_file(Relation grel) +{ + char *filename, + *tempname; + int bufsize; + FILE *fp; + mode_t oumask; + HeapScanDesc scan; + HeapTuple tuple; + TupleDesc dsc = RelationGetDescr(grel); + + /* + * Create a temporary filename to be renamed later. This prevents the + * backend from clobbering the flat file while the postmaster + * might be reading from it. + */ + filename = group_getflatfilename(); + bufsize = strlen(filename) + 12; + tempname = (char *) palloc(bufsize); + snprintf(tempname, bufsize, "%s.%d", filename, MyProcPid); + + oumask = umask((mode_t) 077); + fp = AllocateFile(tempname, "w"); + umask(oumask); + if (fp == NULL) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not write to temporary file \"%s\": %m", + tempname))); + + /* + * Read pg_group and write the file. Note we use SnapshotSelf to + * ensure we see all effects of current transaction. (Perhaps could + * do a CommandCounterIncrement beforehand, instead?) + */ + scan = heap_beginscan(grel, SnapshotSelf, 0, NULL); + while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) + { + Datum datum, + grolist_datum; + bool isnull; + char *groname; + IdList *grolist_p; + AclId *aidp; + int i, + num; + char *usename; + bool first_user = true; + + datum = heap_getattr(tuple, Anum_pg_group_groname, dsc, &isnull); + /* ignore NULL groupnames --- shouldn't happen */ + if (isnull) + continue; + groname = NameStr(*DatumGetName(datum)); + + /* + * Check for illegal characters in the group name. + */ + if (!name_okay(groname)) + { + ereport(LOG, + (errmsg("invalid group name \"%s\"", groname))); + continue; + } + + grolist_datum = heap_getattr(tuple, Anum_pg_group_grolist, dsc, &isnull); + /* Ignore NULL group lists */ + if (isnull) + continue; + + /* be sure the IdList is not toasted */ + grolist_p = DatumGetIdListP(grolist_datum); + + /* scan grolist */ + num = IDLIST_NUM(grolist_p); + aidp = IDLIST_DAT(grolist_p); + for (i = 0; i < num; ++i) + { + tuple = SearchSysCache(SHADOWSYSID, + PointerGetDatum(aidp[i]), + 0, 0, 0); + if (HeapTupleIsValid(tuple)) + { + usename = NameStr(((Form_pg_shadow) GETSTRUCT(tuple))->usename); + + /* + * Check for illegal characters in the user name. + */ + if (!name_okay(usename)) + { + ereport(LOG, + (errmsg("invalid user name \"%s\"", usename))); + continue; + } + + /* + * File format is: "groupname" "user1" "user2" "user3" + */ + if (first_user) + { + fputs_quote(groname, fp); + fputs("\t", fp); + first_user = false; + } + else + fputs(" ", fp); + + fputs_quote(usename, fp); + + ReleaseSysCache(tuple); + } + } + if (!first_user) + fputs("\n", fp); + /* if IdList was toasted, free detoasted copy */ + if ((Pointer) grolist_p != DatumGetPointer(grolist_datum)) + pfree(grolist_p); + } + heap_endscan(scan); + + if (FreeFile(fp)) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not write to temporary file \"%s\": %m", + tempname))); + + /* + * Rename the temp file to its final name, deleting the old flat file. + * We expect that rename(2) is an atomic action. + */ + if (rename(tempname, filename)) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not rename file \"%s\" to \"%s\": %m", + tempname, filename))); + + pfree(tempname); + pfree(filename); +} + + +/* + * write_user_file: update the flat password file + */ +static void +write_user_file(Relation urel) +{ + char *filename, + *tempname; + int bufsize; + FILE *fp; + mode_t oumask; + HeapScanDesc scan; + HeapTuple tuple; + + /* + * Create a temporary filename to be renamed later. This prevents the + * backend from clobbering the flat file while the postmaster might + * be reading from it. + */ + filename = user_getflatfilename(); + bufsize = strlen(filename) + 12; + tempname = (char *) palloc(bufsize); + snprintf(tempname, bufsize, "%s.%d", filename, MyProcPid); + + oumask = umask((mode_t) 077); + fp = AllocateFile(tempname, "w"); + umask(oumask); + if (fp == NULL) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not write to temporary file \"%s\": %m", + tempname))); + + /* + * Read pg_shadow and write the file. Note we use SnapshotSelf to + * ensure we see all effects of current transaction. (Perhaps could + * do a CommandCounterIncrement beforehand, instead?) + */ + scan = heap_beginscan(urel, SnapshotSelf, 0, NULL); + while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) + { + Form_pg_shadow pwform = (Form_pg_shadow) GETSTRUCT(tuple); + HeapTupleHeader tup = tuple->t_data; + char *tp; /* ptr to tuple data */ + long off; /* offset in tuple data */ + bits8 *bp = tup->t_bits; /* ptr to null bitmask in tuple */ + Datum datum; + char *usename, + *passwd, + *valuntil; + + usename = NameStr(pwform->usename); + + /* + * We can't use heap_getattr() here because during startup we will + * not have any tupdesc for pg_shadow. Fortunately it's not too + * hard to work around this. passwd is the first possibly-null + * field so we can compute its offset directly. + */ + tp = (char *) tup + tup->t_hoff; + off = offsetof(FormData_pg_shadow, passwd); + + if (HeapTupleHasNulls(tuple) && + att_isnull(Anum_pg_shadow_passwd - 1, bp)) + { + /* + * It can be argued that people having a null password shouldn't + * be allowed to connect under password authentication, because + * they need to have a password set up first. If you think + * assuming an empty password in that case is better, change this + * logic to look something like the code for valuntil. + */ + continue; + } + + /* assume passwd is pass-by-ref */ + datum = PointerGetDatum(tp + off); + + /* + * The password probably shouldn't ever be out-of-line toasted; + * if it is, ignore it, since we can't handle that in startup mode. + */ + if (VARATT_IS_EXTERNAL(DatumGetPointer(datum))) + continue; + + passwd = DatumGetCString(DirectFunctionCall1(textout, datum)); + + /* assume passwd has attlen -1 */ + off = att_addlength(off, -1, tp + off); + + if (HeapTupleHasNulls(tuple) && + att_isnull(Anum_pg_shadow_valuntil - 1, bp)) + { + /* valuntil is null, emit as an empty string */ + valuntil = pstrdup(""); + } + else + { + /* assume valuntil has attalign 'i' */ + off = att_align(off, 'i'); + /* assume valuntil is pass-by-value, integer size */ + datum = Int32GetDatum(*((int32 *) (tp + off))); + valuntil = DatumGetCString(DirectFunctionCall1(abstimeout, datum)); + } + + /* + * Check for illegal characters in the user name and password. + */ + if (!name_okay(usename)) + { + ereport(LOG, + (errmsg("invalid user name \"%s\"", usename))); + continue; + } + if (!name_okay(passwd)) + { + ereport(LOG, + (errmsg("invalid user password \"%s\"", passwd))); + continue; + } + + fputs_quote(usename, fp); + fputs(" ", fp); + fputs_quote(passwd, fp); + fputs(" ", fp); + fputs_quote(valuntil, fp); + fputs("\n", fp); + + pfree(passwd); + pfree(valuntil); + } + heap_endscan(scan); + + if (FreeFile(fp)) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not write to temporary file \"%s\": %m", + tempname))); + + /* + * Rename the temp file to its final name, deleting the old flat file. + * We expect that rename(2) is an atomic action. + */ + if (rename(tempname, filename)) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not rename file \"%s\" to \"%s\": %m", + tempname, filename))); + + pfree(tempname); + pfree(filename); +} + + +/* + * This routine is called once during database startup, after completing + * WAL replay if needed. Its purpose is to sync the flat files with the + * current state of the database tables. This is particularly important + * during PITR operation, since the flat files will come from the + * base backup which may be far out of sync with the current state. + * + * In theory we could skip rebuilding the flat files if no WAL replay + * occurred, but it seems safest to just do it always. We have to + * scan pg_database to compute the XID wrap limit anyway. + * + * In a standalone backend we pass database_only = true to skip processing + * the user and group files. We won't need them, and building them could + * fail if there's something corrupt in those catalogs. + */ +void +BuildFlatFiles(bool database_only) +{ + ResourceOwner owner; + RelFileNode rnode; + Relation rel; + + /* + * We don't have any hope of running a real relcache, but we can use + * the same fake-relcache facility that WAL replay uses. + */ + XLogInitRelationCache(); + + /* Need a resowner to keep the heapam and buffer code happy */ + owner = ResourceOwnerCreate(NULL, "BuildFlatFiles"); + CurrentResourceOwner = owner; + + /* hard-wired path to pg_database */ + rnode.spcNode = GLOBALTABLESPACE_OID; + rnode.dbNode = 0; + rnode.relNode = RelOid_pg_database; + + /* No locking is needed because no one else is alive yet */ + rel = XLogOpenRelation(true, 0, rnode); + write_database_file(rel); + + if (!database_only) + { +#ifdef NOT_YET + /* XXX doesn't work yet for reasons stated above */ + + /* hard-wired path to pg_group */ + rnode.spcNode = GLOBALTABLESPACE_OID; + rnode.dbNode = 0; + rnode.relNode = RelOid_pg_group; + + rel = XLogOpenRelation(true, 0, rnode); + write_group_file(rel); +#endif + + /* hard-wired path to pg_shadow */ + rnode.spcNode = GLOBALTABLESPACE_OID; + rnode.dbNode = 0; + rnode.relNode = RelOid_pg_shadow; + + rel = XLogOpenRelation(true, 0, rnode); + write_user_file(rel); + } + + CurrentResourceOwner = NULL; + ResourceOwnerDelete(owner); + + XLogCloseRelationCache(); +} + + +/* + * This routine is called during transaction commit or abort. + * + * On commit, if we've written any of the critical database tables during + * the current transaction, update the flat files and signal the postmaster. + * + * On abort, just reset the static flags so we don't try to do it on the + * next successful commit. + * + * NB: this should be the last step before actual transaction commit. + * If any error aborts the transaction after we run this code, the postmaster + * will still have received and cached the changed data; so minimize the + * window for such problems. + */ +void +AtEOXact_UpdateFlatFiles(bool isCommit) +{ + Relation drel = NULL; + Relation grel = NULL; + Relation urel = NULL; + + if (database_file_update_subid == InvalidSubTransactionId && + group_file_update_subid == InvalidSubTransactionId && + user_file_update_subid == InvalidSubTransactionId) + return; /* nothing to do */ + + if (!isCommit) + { + database_file_update_subid = InvalidSubTransactionId; + group_file_update_subid = InvalidSubTransactionId; + user_file_update_subid = InvalidSubTransactionId; + return; + } + + /* + * We use ExclusiveLock to ensure that only one backend writes the + * flat file(s) at a time. That's sufficient because it's okay to + * allow plain reads of the tables in parallel. There is some chance + * of a deadlock here (if we were triggered by a user update of one + * of the tables, which likely won't have gotten a strong enough lock), + * so get the locks we need before writing anything. + */ + if (database_file_update_subid != InvalidSubTransactionId) + drel = heap_openr(DatabaseRelationName, ExclusiveLock); + if (group_file_update_subid != InvalidSubTransactionId) + grel = heap_openr(GroupRelationName, ExclusiveLock); + if (user_file_update_subid != InvalidSubTransactionId) + urel = heap_openr(ShadowRelationName, ExclusiveLock); + + /* Okay to write the files */ + if (database_file_update_subid != InvalidSubTransactionId) + { + database_file_update_subid = InvalidSubTransactionId; + write_database_file(drel); + heap_close(drel, NoLock); + } + + if (group_file_update_subid != InvalidSubTransactionId) + { + group_file_update_subid = InvalidSubTransactionId; + write_group_file(grel); + heap_close(grel, NoLock); + } + + if (user_file_update_subid != InvalidSubTransactionId) + { + user_file_update_subid = InvalidSubTransactionId; + write_user_file(urel); + heap_close(urel, NoLock); + } + + /* + * Signal the postmaster to reload its caches. + */ + SendPostmasterSignal(PMSIGNAL_PASSWORD_CHANGE); +} + +/* + * AtEOSubXact_UpdateFlatFiles + * + * Called at subtransaction end, this routine resets or updates the + * need-to-update-files flags. + */ +void +AtEOSubXact_UpdateFlatFiles(bool isCommit, + SubTransactionId mySubid, + SubTransactionId parentSubid) +{ + if (isCommit) + { + if (database_file_update_subid == mySubid) + database_file_update_subid = parentSubid; + + if (group_file_update_subid == mySubid) + group_file_update_subid = parentSubid; + + if (user_file_update_subid == mySubid) + user_file_update_subid = parentSubid; + } + else + { + if (database_file_update_subid == mySubid) + database_file_update_subid = InvalidSubTransactionId; + + if (group_file_update_subid == mySubid) + group_file_update_subid = InvalidSubTransactionId; + + if (user_file_update_subid == mySubid) + user_file_update_subid = InvalidSubTransactionId; + } +} + + +/* + * This trigger is fired whenever someone modifies pg_database, pg_shadow + * or pg_group via general-purpose INSERT/UPDATE/DELETE commands. + * + * It is sufficient for this to be a STATEMENT trigger since we don't + * care which individual rows changed. It doesn't much matter whether + * it's a BEFORE or AFTER trigger. + */ +Datum +flatfile_update_trigger(PG_FUNCTION_ARGS) +{ + TriggerData *trigdata = (TriggerData *) fcinfo->context; + + if (!CALLED_AS_TRIGGER(fcinfo)) + elog(ERROR, + "flatfile_update_trigger was not called by trigger manager"); + + if (RelationGetNamespace(trigdata->tg_relation) != PG_CATALOG_NAMESPACE) + elog(ERROR, "flatfile_update_trigger was called for wrong table"); + + switch (RelationGetRelid(trigdata->tg_relation)) + { + case RelOid_pg_database: + database_file_update_needed(); + break; + case RelOid_pg_group: + group_file_update_needed(); + break; + case RelOid_pg_shadow: + user_file_update_needed(); + break; + default: + elog(ERROR, "flatfile_update_trigger was called for wrong table"); + break; + } + + return PointerGetDatum(NULL); +} + + +/* + * Old version of trigger --- remove after we can force an initdb + */ +extern Datum update_pg_pwd_and_pg_group(PG_FUNCTION_ARGS); + +Datum +update_pg_pwd_and_pg_group(PG_FUNCTION_ARGS) +{ + return flatfile_update_trigger(fcinfo); +} diff --git a/src/include/access/transam.h b/src/include/access/transam.h index bf52484cc43..e623c5d0006 100644 --- a/src/include/access/transam.h +++ b/src/include/access/transam.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/access/transam.h,v 1.51 2004/12/31 22:03:21 pgsql Exp $ + * $PostgreSQL: pgsql/src/include/access/transam.h,v 1.52 2005/02/20 02:22:03 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -75,13 +75,21 @@ /* * VariableCache is placed in shmem and used by - * backends to get next available XID & OID. + * backends to get next available OID & XID. + * + * Note: xidWrapLimit and limit_datname are not "active" values, but are + * used just to generate useful messages when xidWarnLimit or xidStopLimit + * are exceeded. */ typedef struct VariableCacheData { - TransactionId nextXid; /* next XID to assign */ Oid nextOid; /* next OID to assign */ uint32 oidCount; /* OIDs available before must do XLOG work */ + TransactionId nextXid; /* next XID to assign */ + TransactionId xidWarnLimit; /* start complaining here */ + TransactionId xidStopLimit; /* refuse to advance nextXid beyond here */ + TransactionId xidWrapLimit; /* where the world ends */ + NameData limit_datname; /* database that needs vacuumed first */ } VariableCacheData; typedef VariableCacheData *VariableCache; @@ -118,6 +126,8 @@ extern bool TransactionIdFollowsOrEquals(TransactionId id1, TransactionId id2); /* in transam/varsup.c */ extern TransactionId GetNewTransactionId(bool isSubXact); extern TransactionId ReadNewTransactionId(void); +extern void SetTransactionIdLimit(TransactionId oldest_datfrozenxid, + Name oldest_datname); extern Oid GetNewObjectId(void); extern void CheckMaxObjectId(Oid assigned_oid); diff --git a/src/include/commands/user.h b/src/include/commands/user.h index 882b0efc219..a37f94940a9 100644 --- a/src/include/commands/user.h +++ b/src/include/commands/user.h @@ -4,20 +4,16 @@ * Commands for manipulating users and groups. * * - * $PostgreSQL: pgsql/src/include/commands/user.h,v 1.25 2004/09/16 16:58:39 tgl Exp $ + * $PostgreSQL: pgsql/src/include/commands/user.h,v 1.26 2005/02/20 02:22:05 tgl Exp $ * *------------------------------------------------------------------------- */ #ifndef USER_H #define USER_H -#include "fmgr.h" #include "nodes/parsenodes.h" -extern char *group_getfilename(void); -extern char *user_getfilename(void); - extern void CreateUser(CreateUserStmt *stmt); extern void AlterUser(AlterUserStmt *stmt); extern void AlterUserSet(AlterUserSetStmt *stmt); @@ -29,11 +25,4 @@ extern void AlterGroup(AlterGroupStmt *stmt, const char *tag); extern void DropGroup(DropGroupStmt *stmt); extern void RenameGroup(const char *oldname, const char *newname); -extern Datum update_pg_pwd_and_pg_group(PG_FUNCTION_ARGS); - -extern void AtEOXact_UpdatePasswordFile(bool isCommit); -extern void AtEOSubXact_UpdatePasswordFile(bool isCommit, - SubTransactionId mySubid, - SubTransactionId parentSubid); - #endif /* USER_H */ diff --git a/src/include/utils/flatfiles.h b/src/include/utils/flatfiles.h new file mode 100644 index 00000000000..02e51759254 --- /dev/null +++ b/src/include/utils/flatfiles.h @@ -0,0 +1,33 @@ +/*------------------------------------------------------------------------- + * + * flatfiles.h + * Routines for maintaining "flat file" images of the shared catalogs. + * + * + * $PostgreSQL: pgsql/src/include/utils/flatfiles.h,v 1.1 2005/02/20 02:22:07 tgl Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef FLATFILES_H +#define FLATFILES_H + +#include "fmgr.h" + +extern void database_file_update_needed(void); +extern void group_file_update_needed(void); +extern void user_file_update_needed(void); + +extern char *database_getflatfilename(void); +extern char *group_getflatfilename(void); +extern char *user_getflatfilename(void); + +extern void BuildFlatFiles(bool database_only); + +extern void AtEOXact_UpdateFlatFiles(bool isCommit); +extern void AtEOSubXact_UpdateFlatFiles(bool isCommit, + SubTransactionId mySubid, + SubTransactionId parentSubid); + +extern Datum flatfile_update_trigger(PG_FUNCTION_ARGS); + +#endif /* FLATFILES_H */ |