aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils/init/flatfiles.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/utils/init/flatfiles.c')
-rw-r--r--src/backend/utils/init/flatfiles.c857
1 files changed, 857 insertions, 0 deletions
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);
+}