aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/access/transam/varsup.c113
-rw-r--r--src/backend/access/transam/xact.c48
-rw-r--r--src/backend/bootstrap/bootstrap.c4
-rw-r--r--src/backend/commands/dbcommands.c28
-rw-r--r--src/backend/commands/user.c492
-rw-r--r--src/backend/commands/vacuum.c52
-rw-r--r--src/backend/libpq/hba.c9
-rw-r--r--src/backend/postmaster/postmaster.c18
-rw-r--r--src/backend/tcop/postgres.c9
-rw-r--r--src/backend/utils/init/Makefile4
-rw-r--r--src/backend/utils/init/flatfiles.c857
-rw-r--r--src/include/access/transam.h16
-rw-r--r--src/include/commands/user.h13
-rw-r--r--src/include/utils/flatfiles.h33
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 */